﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Data.SqlTypes;
using System.Linq;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Services;
using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.DataFace;
using Noris.Schedule.Support.Green;
using Noris.Schedule.Support.Core;

using Noris.Schedule.Planning.DataFace;

namespace Noris.Schedule.Planning.ProcessData
{
	// PARTIAL CLASS PlanningDataCls : metody pro interaktivní přeplánování kapacit
	/// <summary>
	/// Základna pro plánování materiálu i kapacit, na základě společného plánování toku materiálu přes existující kapacity
	/// </summary>
	public partial class PlanningDataCls : PlanningComponentCls, IDisposable
	{
		#region INTERAKTIVNÍ POHYB (ZMĚNA ČASU, ZMĚNA ŘÁDKU)
		#region ŘÍZENÍ INTERAKTIVNÍHO PŘEPLÁNOVÁNÍ ČASU A MÍSTA
        /// <summary>
        /// Metoda zjistí, zda v dané chvíli je možno interaktivně editovat data.
        /// To je tehdy, když aktuální verzi plánu needituje ani neukládá nikdo jiný.
        /// </summary>
        /// <returns></returns>
        public bool IsInteractiveEditEnabled()
        {
            return this.CanSetPlanningProcessState(PlanningProcessState.NowEditing);
        }
        /// <summary>
        /// Metoda zjistí, zda v dané chvíli je možno interaktivně editovat data.
        /// To je tehdy, když aktuální verzi plánu needituje ani neukládá nikdo jiný.
        /// Tato metoda vrací text případného problému.
        /// </summary>
        /// <param name="errorInfo">Výstup textu s problémem</param>
        /// <returns></returns>
        public bool IsInteractiveEditEnabled(out string errorInfo)
        {
            return this.CanSetPlanningProcessState(PlanningProcessState.NowEditing, out errorInfo);
        }
        /// <summary>
		/// Zajistí provedení přesunu dané jednotky práce (seznam v moveArgs.ActiveItemList) na požadované časy a kapacitní jednotky.
		/// Do argumentu moveArgs ukládá informace o provedených změnách v datech, aby je vizuální vrstva mohla akceptovat.
		/// </summary>
		/// <param name="moveArgs"></param>
		public void InteractiveMove(PlanningInteractiveMoveArgs moveArgs)
		{
			// Parametr uložím do mé instanční proměnné, protože jej budu potřebovat průběžně používat a hlavně doplňovat:
			this._InterMoveArgs = moveArgs;

            this.SetPlanningProcessState(PlanningProcessState.NowEditing);             // Nastavím stav Edituji data. Pokud to nejde, vyhodím chybu.

            Steward.Diagnostic.InfoClear();
            Steward.Diagnostic.TitleAdd("InteractiveMove data");
            moveArgs.AddToDiagnostic(this, null, null);
            List<CapacityPlanWorkItemCls> taskList = moveArgs.GetTaskList(this);
            
            this.AddToDiagnostic(taskList, "Selected Task before", "  ");

            using (DoOn.End(delegate() { this._InterMoveAutoLinkDone(); this._InterMoveArgs = null; }))   // Uvedené operace (anonymní delegát) se provedou VŽDY na konci bloku using(), a to i po chybě.
            {
                // Příprava pro automatický pohyb linkovaných operací, podle hodnoty moveArgs.AutoMoveLinkedTask:
                this._InterMoveAutoLinkPrepare(moveArgs.AutoMoveLinkedTask);

                // Přesun může být definován buď pomocí WorkUnit = element (nejnižší jednotka práce), anebo lze přesouvat celý task.
                // Lze kombinovat obě zadání (ačkoliv to je netypické).
                // Varianta A: zadány jsou celé tasky a jejich TimeVectory:
                foreach (KeyValuePair<GID, TimeVector> taskVect in moveArgs.MoveTaskVectorDict)
                    this._InterMoveOneTaskByTime(taskVect.Key, taskVect.Value, true);

                // Varianta B: zadány jsou jednotlivé WorkItems:
                // Sestavím si seznam párů (vstupní data = WorkUnit), a pro ně spustím přeregistraci:
                List<_MoveItemUnitCls> changeItemList = _InterMoveFindWorkUnits(moveArgs.ActiveItemList);
                foreach (_MoveItemUnitCls itemUnit in changeItemList)
                    this._InterMoveOneTaskByItem(itemUnit);


                // Provedení automatického pohybu linkovaných operací:
                this._InterMoveAutoLinkRun();

                moveArgs.ResultElementLinkListChanged = !this.LinkedElementListValid;   // Změna LinkListu = když není validní (to je, když došlo k jeho změně a resetu)
            }

            this.AddToDiagnostic(taskList, "Selected Task after", "  ");

            Steward.Diagnostic.InfoAddSeparatorLine('=');

            Steward.Diagnostic.Save();
		}
        /// <summary>
        /// Vloží do diagnostiky data o kapacitních úkolech
        /// </summary>
        /// <param name="taskList"></param>
        internal void AddToDiagnostic(IEnumerable<CapacityPlanWorkItemCls> taskList, string title, string rowPrefix)
        {
            Steward.Diagnostic.SubTitleAdd(title);
            string pref = (rowPrefix == null ? "" : rowPrefix);
            foreach (CapacityPlanWorkItemCls task in taskList)
                task.AddToDiagnostic(this, null, pref);
        }
		/// <summary>
		/// Metoda vyhledá a zkontroluje WorkUnit, na které ukazují položky z dodaného seznamu editovaných elementů.
		/// Vyhledání WorkUnit musí proběhnout před tím, než se začnou provádět přesuny dat, 
		/// protože přesunem jednoho elementu může dojít i k přesunu elementů dalších (návazných) a později by je nebylo možno najít.
        /// Metoda vrací seznam položek _MoveItemUnitCls (obsahuje: vstupní element, odpovídající položku WorkUnit, a její lokátor).
		/// </summary>
		/// <param name="activeItemList"></param>
		/// <returns></returns>
		private List<_MoveItemUnitCls> _InterMoveFindWorkUnits(List<PlanningInteractiveMoveActiveItem> activeItemList)
		{
			List<_MoveItemUnitCls> result = new List<_MoveItemUnitCls>();
			foreach (PlanningInteractiveMoveActiveItem activeItem in activeItemList)
			{
				// Pokud není změna na vstupu, a není požadavek fixace, pak tento element do seznamu nebudu zařazovat:
				if (activeItem == null || (!activeItem.IsChanged && !this._InterMoveArgs.SetFixedTask)) continue;

                // Zkontroluji zadaná data (čas, CUnit a CSource) v activeItem:
                this._InterCheckMoveItem(activeItem);

				// Najdu položku práce WorkUnit:
				WorkUnitCls workUnit = this.AxisHeap.FindIWorkItem(activeItem.WorkID);
				this._InterCheckWorkUnit(workUnit, activeItem);

				// Sestavím lokátor položky WorkUnit:
				//  Lokátor je "pozice" položky WorkUnit v jejím tasku, 
				//  a podle tohoto lokátoru je možné dohledat odpovídající WorkUnit i po přeregistraci na jiný čas a/nebo kapacitní jednotku.
				//  Lokátor ukládá relativní pozici času, kapacitní zdroj a další data, a ignoruje klíče WorkID atd.
				WorkUnitLocationCls workLocation = workUnit.WorkUnitLocation;

				result.Add(new _MoveItemUnitCls(activeItem, workUnit, workLocation));
			}

			// Seznam setřídím tak, aby se nejprve prováděly přesuny v čase a potom přesuny na jinou KPJ.
			// Důvod: Přesuny v čase pocházejí od aktivních elementů, a mohou popisovat i přesun na jinou KPJ.
			//        Takové přesuny pravděpodobně zajistí i přesun sousedních elementů, i na jinou KPJ.
			// Kdežto přesuny jen na jinou KPJ jsou menšího rozsahu, a mohou se týkat "závislých" elementů, 
			//        a mohly být přeregistrovány automaticky.
			// Pokud bych nejdřív provedl přesun na jinou KPJ (na podřízeném elementu), 
			//  a pak bych chtěl chtěl přesunut hlavní element té samé větve na jiný čas,
			//  pak bych tento aktivní element nemusel najít, protože by už byl přeregistrovaný na jinou KPJ, a měl by jiné číslo WorkID.
			result.Sort(_MoveItemUnitCls.SortByPriority);

			return result;
		}
		/// <summary>
		/// Vnitřní třída pro zachycení páru položek: ActiveItem (zadání k pohybu), WorkUnit (data která se mají přesunout).
		/// </summary>
		private class _MoveItemUnitCls
		{
			internal _MoveItemUnitCls(PlanningInteractiveMoveActiveItem activeItem, WorkUnitCls workUnit, WorkUnitLocationCls workLocation)
			{
				this.ActiveItem = activeItem;
				this.WorkUnit = workUnit;
				this.WorkLocation = workLocation;
			}
			/// <summary>
			/// Vizualizace
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				return "Item: " + this.ActiveItem.ToString() + ";    WorkUnit: " + this.WorkUnit.ToString();
			}
			internal readonly PlanningInteractiveMoveActiveItem ActiveItem;
			internal readonly WorkUnitCls WorkUnit;
			internal readonly WorkUnitLocationCls WorkLocation;
			/// <summary>
			/// Komparátor podle priority ke zpracování.
			/// Dříve se budou zpracovávat položky se změnou času.
			/// </summary>
			/// <param name="a"></param>
			/// <param name="b"></param>
			/// <returns></returns>
			internal static int SortByPriority(_MoveItemUnitCls a, _MoveItemUnitCls b)
			{
				int at = (a.ActiveItem.IsChangedTime ? 0 : 1);     // Je změněn čas na položce "a" (0=ano), pro třídění: 0 bude dříve než 1
				int bt = (b.ActiveItem.IsChangedTime ? 0 : 1);
				return at.CompareTo(bt);
			}
		}
		/// <summary>
		/// Zajistí provedení přesunu dané jednotky práce (parametr activeItem) na požadovaný čas a kapacitní jednotku.
		/// Současně je předáván WorkUnit, který byl vyhledán ještě před zahájením přesunů.
		/// </summary>
		/// <param name="itemUnit">Kombinace dat, popisuje jak zadání pohybu (ActiveItem), tak původní WorkUnit, tak i jeho lokátor.</param>
		private void _InterMoveOneTaskByItem(_MoveItemUnitCls itemUnit)
		{
			PlanningInteractiveMoveActiveItem activeItem = itemUnit.ActiveItem; // Zadání pro změnu na této jedné položce (čas, jednotka, zdroj)

			// Pokud není změna na vstupu, nic nedělám:
			if (!activeItem.IsChanged && !this._InterMoveArgs.SetFixedTask)
				return;

			// Do tabulky řádků se změnou přidám řádek, který je cílovou KPJ pro element:
			this._InterArgs.AddChangedRow(new GID(PlanUnitCCls.ClassNr, activeItem.CapacityUnitCurrent));

			// Najdeme WorkUnit (to není tak jednoduché, viz specializovaná metoda):
			WorkUnitCls workUnitCurrent = this._InterMoveFindWorkUnit(itemUnit);
			if (workUnitCurrent == null)
				return;

			// Zapamatovat si proměnné, časem (odregistrování a odpojení WorkUnit z Tasku) zmizí:
            CapacityPlanWorkItemCls task = workUnitCurrent.TaskData;             // Reference na task, v němž probíhají požadované změny (ony změny mohou proběhnout i v jiných, ale tady začínají).

            using (DoOn.End(delegate() { this._InteractiveMoveDone(); }))        // Na konci using (i po chybě) provedeme akci: this._InteractiveMoveDone();
            {
                if (activeItem.IsChanged)
                {	// Je požadován pohyb:
                    GID taskGId = task.TaskGID;

                    // Zaregistruji tento task + případně jeho linkované sousedy do seznamu:
                    this._InterMoveAutoLinkContact(taskGId, true);

                    // Připravím si soupis ID všech WorkUnit, které patří do nejširšího okolí editované položky, a připravím kapacitní systém na interaktivní spolupráci:
                    this._InteractiveMovePrepare(workUnitCurrent);

                    // Zajistíme provedení pohybu uvnitř aktuální operace = InnerPass():
                    this._InterMoveOneInnerPass(workUnitCurrent, activeItem);
                    workUnitCurrent = null;       // Od této chvíle je workUnit k ničemu, protože ze svého Parenta je odregistrován.

                    // Rozběhneme vlnu kontrol návazných záznamů (sousední operace, záznamy na ose) do budoucnosti i do minulosti:
                    this._InterMoveOneOuterPush(taskGId);

                    // Task si po změně aktualizuje svoje hlavičkové údaje (časy, kapacitní jednotka, pracoviště):
                    task.RecalcTaskByInnerCapacity();    // Prakticky se nic neprovede, metoda je empty.
                }

                // Pokud je požadovaná fixace tasku, bude se týkat aktuální operace (task) a případně i jeho cílové osy (axis).
                this._InterFixAdd(task.TaskID, this._InterMoveArgs.SetFixedTask);
            }
		}
        /// <summary>
        /// Provede přeplánování celého tasku (všech jeho Pass) na daný časový vektor.
        /// </summary>
        /// <param name="taskGId">GID tasku = operace</param>
        /// <param name="timeVector">Čas a směr registrace</param>
        /// <param name="asActive">Příznak, že jde o aktivně pohybovaný task (true) nebo task posouvaný z důvodu Linked Group (false)</param>
        private void _InterMoveOneTaskByTime(GID taskGId, TimeVector timeVector, bool asActive)
        {
            // Najdu task:
            CapacityPlanWorkItemCls task = this.FindTaskC(taskGId);
            if (task == null)
                return;

            // Provedu posun:
            this._InterMoveOneTaskByTime(task, timeVector, asActive);
        }
        /// <summary>
        /// Provede přeplánování celého tasku (všech jeho Pass) na daný časový vektor.
        /// </summary>
        /// <param name="taskGId">GID tasku = operace</param>
        /// <param name="timeVector">Čas a směr registrace</param>
        /// <param name="asActive">Příznak, že jde o aktivně pohybovaný task (true) nebo task posouvaný z důvodu Linked Group (false)</param>
        private void _InterMoveOneTaskByTime(CapacityPlanWorkItemCls task, TimeVector timeVector, bool asActive)
        {
            // Kontrola, zda je nutno task přeregistrovávat (z hlediska času požadovaného a času tasku aktuálního):
            //  (pokud task pro směr ToFuture začíná v daném čase, anebo pro směr ToHistory končí v daném čase, pak se nebude měnit):
            if (task.TimeWorkMatchTimeVector(timeVector))
                return;

            using (DoOn.End(delegate() { this._InteractiveMoveDone(); }))        // Na konci using (i po chybě) provedeme akci: this._InteractiveMoveDone();
			{
                // Zaregistruji tento task + případně jeho linkované sousedy do seznamu:
                if (asActive)
                    this._InterMoveAutoLinkContact(task, true);

                // Připravím si soupis ID všech WorkUnit, které patří do nejširšího okolí editované položky, a připravím kapacitní systém na interaktivní spolupráci:
				this._InteractiveMovePrepare(task);

				// Zajistíme přeregistrování daného tasku:
                this._InterMoveOneTaskByTimeInner(task, timeVector);
               
				// Rozběhneme vlnu kontrol návazných záznamů (sousední operace, záznamy na ose) do budoucnosti i do minulosti:
                this._InterMoveOneOuterPush(task.TaskGID);

				// Pokud je požadovaná fixace tasku, bude se týkat aktuální operace (task) a případně i jeho cílové osy (axis).
                if (asActive)
    				this._InterFixAdd(task.TaskID, this._InterMoveArgs.SetFixedTask);
			}
        }
        /// <summary>
        /// Přeregistruje daný task na daný čas a směr, pouze tento jeden task.
        /// Jde o inner metodu = neřeší this._InteractiveMovePrepare() a this._InteractiveMoveDone().
        /// Neřeší ani kontrolu a posunutí sousedních záznamů (this._InterMoveOneOuterPush()).
        /// </summary>
        /// <param name="task"></param>
        /// <param name="timeVector"></param>
        private void _InterMoveOneTaskByTimeInner(CapacityPlanWorkItemCls task, TimeVector timeVector)
        {
            InteractiveAdjacentArgs adjArgs = InteractiveAdjacentArgs.CreateForTask(timeVector.Direction, this._InterMoveArgs);
            this._InterMoveOneOuterMoveTask(task, timeVector.Date, adjArgs);
        }
        /// <summary>
		/// Metoda najde WorkUnit, která se má editovat.
		/// Nejde jen o to, najít WorkUnit podle jeho WorkID, ale případně se i rozhodnutí,
		/// zda hledat WorkUnit nově založený - po přeregistraci v některém předchozím kroku.
		/// Pozor: metoda může WorkUnit "nenajít", pak vrátí null.
		/// </summary>
		/// <param name="itemUnit"></param>
		/// <returns></returns>
		private WorkUnitCls _InterMoveFindWorkUnit(_MoveItemUnitCls itemUnit)
		{
			// Najdeme znovu WorkUnit : v průběhu přeregistrace v předchozích krocích 
			//  mohla být tato WorkUnit odebrána (odregistrovaná), a tak ji už nebudeme předělávat nyní:
			WorkUnitCls workUnitCurrent = this.AxisHeap.FindIWorkItem(itemUnit.ActiveItem.WorkID);
			if (workUnitCurrent != null)
				return workUnitCurrent;

			// Podle klíče WorkID jsme WorkUnit nenašli. A protože jsme tady, znamená to že na začátku existovala.
			// Z toho vyplývá, že se tato WorkUnit odregistrovala teprve v tomto procesu.
			// Pokud není plánovaná změna kapacitní jednotky, pak nebudeme dohledávat nově zaregistrovanou WorkUnit, a skončím:
			if (!itemUnit.ActiveItem.IsChangedUnit)
				return null;

			// Položka je tedy odregistrovaná, a přitom byl požadavek na změnu kapacitní jednotky.
			// Zkusíme najít jednotku WorkUnit podle lokátoru, a zjistíme zda je změna kapacitní jednotky stále potřebná:
			workUnitCurrent = this._AxisHeap.FindIWorkItem(itemUnit.WorkLocation);
			if (workUnitCurrent == null)
				return null;

			// Pokud na nynější položce WorkUnit už je odpovídající kapacitní jednotka, nebudeme dělat nic:
			if (workUnitCurrent.CapacityUnit == itemUnit.ActiveItem.CapacityUnitCurrent)
				return null;

			// Tuto položku WorkUnit bychom měli přesunout na požadovanou kapacitní jednotku, ale neměli bychom měnit čas!:
			itemUnit.ActiveItem.ResetTime();

			return workUnitCurrent;
		}
		/// <summary>
		/// Předávací metoda pro kontrolu návazných záznamů k záznamu daného Tasku, v daném směru.
		/// Metoda zajistí časovou kontinuitu všech záznamů, případně je přeregistruje na vyhovující časy.
		/// Změny se mimo jiné projevují i na skladové časové ose (vývoj zásoby), ale metoda nenavrhuje nové akce na zajištění.
		/// Důsledkem tedy může být nedostatečná zásoba materiálu/polotovarů, pokud je do výroby plánováno použít skladovou zásobu.
		/// Pokud je plánováno využít výroby komponent = polotovarů, pak tato podřízená výroba bude posunuta.
		/// </summary>
		/// <param name="taskGId">GID záznamu, jehož sousedi na obě strany se mají zkontrolovat, podle parametrů v this._InterMoveArgs</param>
		private void _InterMoveOneOuterPush(GID taskGId)
		{
			PlanningInteractiveMoveArgs moveArgs = this._InterMoveArgs;

			this._InteractiveShiftAdjacent(taskGId, InteractiveAdjacentArgs.CreateForTree(TimeRange.TimeDirection.ToFuture, moveArgs));
			this._InteractiveShiftAdjacent(taskGId, InteractiveAdjacentArgs.CreateForTree(TimeRange.TimeDirection.ToHistory, moveArgs));
		}
        /// <summary>
        /// Ověří, zda data zadaná do požadavku na přesun kapacitního úkolu jsou korektní
        /// </summary>
        /// <param name="activeItem"></param>
        private void _InterCheckMoveItem(PlanningInteractiveMoveActiveItem activeItem)
        {
            if (activeItem.CapacityUnitCurrent != 0 && this.CapacityData.FindCapacityUnit(activeItem.CapacityUnitCurrent) == null)
                Throw.SysError(MessageInfo.Get("Chyba: Je požadován přesun na kapacitní jednotku %0, která neexistuje.", activeItem.CapacityUnitCurrent));
            if (activeItem.CapacitySourceCurrent != 0 && this.CapacityData.FindCapacitySourceData(activeItem.CapacitySourceCurrent) == null)
                Throw.SysError(MessageInfo.Get("Chyba: Je požadován přesun na zdroj kapacit %0, který neexistuje.", activeItem.CapacitySourceCurrent));
        }
		/// <summary>
		/// Metoda prověří, zda nalezená jednotka práce (v datech plánovacího procesu) odpovídá zadanému editovanému elementu.
		/// </summary>
		/// <param name="workUnit"></param>
		/// <param name="activeItem"></param>
		private void _InterCheckWorkUnit(WorkUnitCls workUnit, PlanningInteractiveMoveActiveItem activeItem)
		{
			if (workUnit == null)
				Throw.SysError(MessageInfo.Get("Chyba: editovaný element nebyl v datech plánovacího procesu nalezen."));

			if (workUnit.UnitID != activeItem.CapacityUnitOrigin)
				Throw.SysError(MessageInfo.Get("Chyba: kapacitní plánovací jednotka na editovaném elementu neodpovídá plánovacímu procesu. Je %0, má být %1.",
					_GetCapacityUnitText(activeItem.CapacityUnitOrigin), _GetCapacityUnitText(workUnit.UnitID)));

			if (workUnit.WorkTime != activeItem.TimeRangeOrigin)
				Throw.SysError(MessageInfo.Get("Chyba: časový interval na editovaném elementu neodpovídá plánovacímu procesu. Je %0, má být %1.",
					activeItem.TimeRangeOrigin, workUnit.WorkTime));
		}
		#endregion
		#region POMOC PŘI VYHLEDÁNÍ OBECNÉHO ZDROJE KAPACIT, KTERÝ NEJLÉPE ODPOVÍDÁ DANÉ KPJ
		/// <summary>
		/// Metoda najde a vrátí klíč obecného zdroje kapacit, který představuje daná kapacitní jednotka.
		/// Metoda prohledá vztahy mezu kapacitní jednotkou a obecným zdrojem, a z nich vybere ten s nejvyšší prioritou .
		/// Vrací klíč (subjekt) obecného zdroje kapacit. Pokud nenajde KPJ, nebo tato nemá žádné vztahy, vrací 0.
		/// </summary>
		/// <param name="planUnitC">Klíč KPJ</param>
		/// <returns></returns>
		public int FindTopCapacitySourceForUnit(int planUnitC)
		{
			CapacityUnitCls unit = this.CapacityData.GetCapacityUnit(planUnitC);
			if (unit == null) return 0;
			PlanCSourceLinkCls link = unit.FindTopCSourceLink();
			return (link == null ? 0 : link.CSource);
		}
		#endregion
		#region INTERAKTIVNÍ PŘEPLÁNOVÁNÍ UVNITŘ JEDNÉ OPERACE
		/// <summary>
		/// Metoda zajistí posunutí vybrané položky WorkUnit na nové místo v čase a na požadovanou plánovací jednotku,
		/// a zajistí návazné pohyby položek WorkTime v rámci nadřízeného průchodu operací = WorkPass.
		/// Dále zajistí přepočet termínů nadřízené osy S, a to s vynulováním času požadavku 
		///  = termíny na ose S budou čistě odpovídat souhrnu času operací, protože pohyb operací je prováděn "odprostředka", interaktivně, a ne návazně odjinud.
		/// Tato metoda pracuje výhradně s prvním aktivním prvkem, nikdy není volaná pro návazné prvky 
		/// (to je dostatečně zřejmé z typu parametru: PlanningInteractiveMoveActiveItem)
		/// </summary>
		/// <param name="workUnit"></param>
		/// <param name="activeItem"></param>
		private void _InterMoveOneInnerPass(WorkUnitCls workUnit, PlanningInteractiveMoveActiveItem activeItem)
		{
			// Získám referenci na WorkPass, v jehož rámci se bude změna provádět:
			WorkPassCls workPass = workUnit.WorkPass;

			// Ze všech dostupných údajů sestavím podklady pro odregistrování položky času a pro registraci kapacit po změně:
			CapacityTimeRegData timeData = this._InterGetTimeDataInput(workUnit, activeItem, this._InterMoveArgs.CapacityLimitForActiveTask);

			// Zavolám metodu, která zajistí odregistrování položky WorkTime (specifikováno v timeData) a novou registraci, a následné kontroly:
			InteractiveAdjacentArgs adjArgs = InteractiveAdjacentArgs.CreateForTask(TimeRange.TimeDirection.ToFuture, this._InterMoveArgs);
			this._InterMoveOneInnerTime(workPass, timeData, adjArgs);
			workUnit = null;                // Od této chvíle je workUnit k ničemu, protože ze svého Parenta je odregistrován.

			// Zajistím přidání nově umístěného elementu do argumentu _InterMoveArgs, pro aktivaci sítě vztahů po dokončení editace:
			this._InterAddEditActiveItem(workPass, timeData);

			// Zajistíme přepočet termínů na ose S, která je parentem zdejší operace, a to s vynulováním Request datumu:
			this._InterRecalcAxisTimeFromTasks(workPass.Parent.AxisGID, true);
		}
		/// <summary>
		/// Metoda najde v daném průchodu takový prvek WorkUnit, který odpovídá zadanému požadavku na editaci (řádek, čas),
		/// a jeho pointer přidá do výstupních dat v argumentu _InterMoveArgs.
		/// </summary>
		/// <param name="workPass">Průchod, v němž se editace stala</param>
		/// <param name="timeData">Data, popisující požadavky na registraci</param>
		private void _InterAddEditActiveItem(WorkPassCls workPass, CapacityTimeRegData timeData)
		{
			// Najdu WorkTime, vyhovující časovému vektoru pohybu (kam to chtěl uživatel přesunout):
			WorkTimeCls workTime = workPass.FindWorkTime(timeData.MoveVector, true, true);
			if (workTime == null) return;

			// Najdu WorkUnit v rámci nalezeného WorkTime, na základě dat o KPJ, pomocí lokátoru:
			WorkUnitLocationCls locator = new WorkUnitLocationCls(workPass.TaskID, workPass.PassID, workPass.TaskData.OperationKey, workTime.Phase,
				timeData.Category, timeData.CapacitySourceAssign, timeData.CapacityUnitAssign, -1, 1M, 0M, 1M);
			WorkUnitCls workUnit = workTime.FindUnitItem(locator);

			this._InterMoveArgs.AddEditActiveItem(workUnit.DataPointer);
		}
		/// <summary>
		/// Klíčová metoda pro přeplánování určitých položek WorkTime na jiný čas a kapacitní jednotky.
		/// Pracuje se s průchodem workPass.
		/// Odregistrují se položky času WorkTime dané v timeData.
		/// Zaregistruje se kapacita + fáze + čas a směr podle dat v timeData.
		/// Zajistí se kontroly sousedních stávajících položek WorkTime, případně se přeregistrují tak, aby byly časově vyhovující.
		/// Pokud dojde k reálné změně dat, zajistí invalidaci řádku osy S.
		/// Plní data diagnostiky (ale nedělá jejich Clear a ani InfoToClipboard().
		/// </summary>
		/// <param name="workPass">Data o jednom průchodu operací</param>
		/// <param name="timeData">Požadavek na přeregistraci kapacity. Obsahuje typ omezení kapacit pro svůj vlastní čas.</param>
		/// <param name="limitedCType">Typ omezení kapacit pro sousední položky času (WorkTime v rámci téhož WorkPass).</param>
		private void _InterMoveOneInnerTime(WorkPassCls workPass, CapacityTimeRegData timeData, InteractiveAdjacentArgs adjArgs)
		{
			// Diagnostika:
            workPass.AddToDiagnostic(this, "Výchozí obsah WorkTimeList", "");
            timeData.AddToDiagnostic(this, "Změna registrace WorkTime", "");

			// Odebrat a odregistrovat položky, a nově zaregistrovat kapacitu:
			bool isChange = this._InterMoveOneTimeChange(workPass, timeData);

			// Uložím si indexy nově vložených položek Time po první registraci:
			int indexFrom = workPass.TimeRegisterInsertFromIndex;      // Index prvního vloženého Time. Je třeba načíst nyní, ne později !!!
			int indexTo = workPass.TimeRegisterInsertToIndex;          // Index posledního vloženého Time. Je třeba načíst nyní, ne později !!!

			// Zajistím, aby nové položky WorkTime v rámci tohoto WorkPass nebyly konfliktní s položkami stávajícími (ToFuture, ToHistory),
			//  pokud by byly v konfliktu, tak posunu staré položky (počínaje indexem indexTo + ToFuture / indexFrom - ToHistory):
			this._InterMoveOneInnerPush(workPass, indexTo, adjArgs.CloneForTimeDir(TimeRange.TimeDirection.ToFuture), ref isChange);
			this._InterMoveOneInnerPush(workPass, indexFrom, adjArgs.CloneForTimeDir(TimeRange.TimeDirection.ToHistory), ref isChange);

			// Pokud došlo ke změně na tomto workPass, zajistíme invalidaci odpovídajících řádků v grafech:
			if (isChange)
				this._InterMoveInvalidateRowForTask(workPass.Parent);

			// Pro jistotu setřídím položky WorkTime uvnitř WorkPass:
			workPass.WorkTimeListSort();

			// Diagnostika:
            workPass.AddToDiagnostic(this, "Konečný obsah WorkTimeList", "");
		}
		/// <summary>
		/// Metoda zajistí zadání invalidace řádku (řádků) grafu, které vizuálně prezentují daný task.
		/// </summary>
		/// <param name="taskData"></param>
		private void _InterMoveInvalidateRowForTask(CapacityPlanWorkItemCls taskData)
		{
			this._InterArgs.AddChangedRow(taskData.AxisData.AxisGID);
			this._InterArgs.AddChangedRow(taskData.AxisData.AxisGIDSubRow);
		}
		/// <summary>
		/// Metoda, která připraví argument pro první registraci času, která akceptuje vstupní interaktivní požadavek uživatele.
		/// Z požadavku načte požadovaný čas a kapacitní jednotku, zjistí požadovaný posun času.
		/// Zjistí, v jakém vztahu je požadovaný časový interval ke stavům kapacit na požadované kapacitní jednotce.
		/// Určí počáteční čas registrace kapacit a směr registrace.
		/// Vrátí datový objekt CapacityTimeRegData, který slouží jako podklad pro registraci kapacit jednotky času.
		/// </summary>
		/// <param name="workUnit"></param>
		/// <param name="activeItem"></param>
		/// <param name="limitedCType">Typ omezení kapacit pro přeregistraci jednotky</param>
		/// <returns></returns>
		private CapacityTimeRegData _InterGetTimeDataInput(WorkUnitCls workUnit, PlanningInteractiveMoveActiveItem activeItem, LimitedCType limitedCType)
		{
			CapacityFindFreeTimeArgs findArgs = new CapacityFindFreeTimeArgs();

			findArgs.TimeVector = new TimeVector(activeItem.TimeRangeCurrent.Begin, TimeRange.TimeDirection.ToFuture);
			bool existsCLevelAtBegin = this._InterCLevelExists(activeItem.CapacityUnitCurrent, findArgs);

			findArgs.TimeVector = new TimeVector(activeItem.TimeRangeCurrent.End, TimeRange.TimeDirection.ToHistory);
			bool existsCLevelAtEnd = this._InterCLevelExists(activeItem.CapacityUnitCurrent, findArgs);
			
			// Výchozí směr je daný směrem posunu aktivního elementu (kam s ním uživatel šoupnul):
			TimeRange.TimeDirection moveTimeDir = activeItem.MoveDir;
			// Pokud uživatel přesunul položku na jiný řádek, ale nezměnil čas, pak by mělo dojít k přeregistraci - a k tomu je zapotřebí určit směr času!
			if (moveTimeDir == TimeRange.TimeDirection.Stay)
				moveTimeDir = TimeRange.TimeDirection.ToFuture;
			// Výchozí čas daný prostým pohybem (pro přesun ToFuture je počátek daný časem Begin požadavku, ToHistory = End):
			DateTime moveStartTime = activeItem.TimeRangeCurrent.Edge(moveTimeDir, true);   // Pro ToFuture vrátí Begin, pro ToHistory vrátí End.

			// Výchozí směr alokace kapacit je primárně podle směru posunu elementu (kromě výjimek, viz dál):
			TimeRange.TimeDirection registerStartDir = moveTimeDir;
			// Za určité podmínky je směr aloakce opačný, a to tehdy, když výchozí směr by začínal mimo stav kapacit, a končil v existujícím stavu kapacit.
			// Jinými slovy: pokud posouvám do budoucího času, 
			//    a posunu element tak, že začíná mimo stav kapacit, a končí v existujícím stavu kapacit,
			//    pak registrace kapacit poběží od toho konce, který je posunut do existujícího stavu kapacit, směrem ToHistory:
			if (moveTimeDir == TimeRange.TimeDirection.ToFuture && !existsCLevelAtBegin && existsCLevelAtEnd)
				registerStartDir = TimeRange.TimeDirection.ToHistory;
			else if (moveTimeDir == TimeRange.TimeDirection.ToHistory && existsCLevelAtBegin && !existsCLevelAtEnd)
				registerStartDir = TimeRange.TimeDirection.ToFuture;

			// Výchozí čas registrace = podle směru alokace:
			DateTime registerStartTime = activeItem.TimeRangeCurrent.Edge(registerStartDir, true);      // Pro ToFuture vrátí Begin, pro ToHistory vrátí End.

			// Data pro řízení registrace kapacit tohoto času:
			CapacityTimeRegData timeData = new CapacityTimeRegData(workUnit.Parent.Phase, workUnit.Parent.PassCapacity, limitedCType, registerStartDir, registerStartTime, moveTimeDir, moveStartTime);

			// Data pro řízení pohybu mezi kapacitními jednotkami i mezi obecnými zdroji kapacit:
			timeData.StoreUnitChange(activeItem, workUnit);

			// Data pro odregistrování kapacity vstupního prvku:
			timeData.UnregisterWorkTimeList = new List<WorkTimeCls>();
			timeData.UnregisterWorkTimeList.Add(workUnit.Parent);

			return timeData;
		}
		/// <summary>
		/// Metoda, která připraví argument pro registraci času na základě existujícího časového úseku (podle dřívějšího WorkTime).
		/// Vrátí datový objekt CapacityTimeRegData, který slouží jako podklad pro novou registraci kapacit pro tentýž úkol, ale jiný čas.
		/// </summary>
		/// <param name="workTime">Původní WorkTime (načtu z něj: fázi, kapacitu)</param>
		/// <param name="timeDir">Směr nové registrace času</param>
		/// <param name="startTime">Nový počátek registrace času</param>
		/// <param name="limitedCType">Typ omezení kapacit pro tento úsek</param>
		/// <returns></returns>
		private CapacityTimeRegData _InterGetTimeDataWorkTime(WorkTimeCls workTime, TimeRange.TimeDirection timeDir, DateTime startTime, LimitedCType limitedCType)
		{
			// Data pro registraci kapacity = vycházíme z dané položky WorkTime, a z dodaného času a směru:
			CapacityTimeRegData timeData = new CapacityTimeRegData(workTime.Phase, workTime.PassCapacity, limitedCType, timeDir, startTime, timeDir, startTime);

			// Data pro odregistrování kapacity = dodaná položka WorkTime:
			timeData.UnregisterWorkTimeList = new List<WorkTimeCls>();
			timeData.UnregisterWorkTimeList.Add(workTime);

			return timeData;
		}
		/// <summary>
		/// Metoda zjistí, zda pro danou kapacitní jednotku v daném čase a směru existuje stav kapacit.
		/// Neřeší jeho obsazenost.
		/// Slouží k určení vhodného směru alokace kapacit poté, kdy uživatel posunul určitý WorkUnit na určitý časový interval (kamkoliv).
		/// 
		/// </summary>
		/// <param name="planUnitC">Kapacitní jednotka</param>
		/// <param name="findArgs">Parametry hledání času</param>
		/// <returns></returns>
		private bool _InterCLevelExists(int planUnitC, CapacityFindFreeTimeArgs findArgs)
		{
			CapacityLevelItemCls cLevel = this.CapacityData.FindCapacityLevelForTime(planUnitC, findArgs, false);
			TimeRange timeRange = cLevel.Time;                      // Časový interval stavu kapacit
			return timeRange.Contain(findArgs.TimeVector.Date);     // true = obsahuje požadované datum.
		}
		#endregion
		#region METODY PRO PŘEREGISTROVÁNÍ KAPACITY PO JEDNOTKÁCH ČASU WorkTime NA ZÁKLADĚ DAT V OBJEKTU TŘÍDY CapacityTimeRegData
		/// <summary>
		/// Metoda zajistí přeregistrování kapacit v tasku tak, aby task jako celek vyhovoval danému času, 
		/// ale přitom se neprováděla kompletní přeregistrace celého tasku.
		/// Důvod: pokud budu mít Task roztrhaný v čase na tři WorkTime (0-2; 8-12; 20-22), a přitom okolo bude volno (což může být),
		/// a budu potřebovat přeregistrovat tak, aby počátek se posunul na čas 4, pak očekávám výsledek:
		/// (4-6; 8-12; 20-22) = posune se jen to nejnutnější (první položka: 0-2 na 4-6), ale další ne = zůstanou tam kde byly.
		/// Pokud bych nejprve odregistroval celý task a pak jej zaregistroval nově, měl bych výsledek 4-10 (sečtený čas), 
		/// a to v interaktivním režimu nechci.
		/// </summary>
		/// <param name="task">Celý task = více průchodů</param>
		/// <param name="startTime">Výchozí čas</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private bool _InterMoveOneOuterMoveTask(CapacityPlanWorkItemCls task, DateTime startTime, InteractiveAdjacentArgs adjArgs)
		{
			bool isChange = false;
			if (!task.IsFixedTask)
			{
                this._InterMoveAutoLinkContact(task, false);
                foreach (WorkPassCls workPass in task.WorkPassList)
				{
					bool isChangeOne = this._InterMoveOneOuterMovePass(workPass, startTime, adjArgs);
					if (isChangeOne && !isChange)
						isChange = true;
				}
			}
			return isChange;
		}
		/// <summary>
		/// Metoda zajistí přeregistrování kapacit v průchodu tak, aby WorkPass jako celek vyhovoval danému času, 
		/// ale přitom se neprováděla kompletní přeregistrace celého tasku.
		/// Důvod: pokud budu mít Task roztrhaný v čase na tři WorkTime (0-2; 8-12; 20-22), a přitom okolo bude volno (což může být),
		/// a budu potřebovat přeregistrovat tak, aby počátek se posunul na čas 4, pak očekávám výsledek:
		/// (4-6; 8-12; 20-22) = posune se jen to nejnutnější (první položka: 0-2 na 4-6), ale další ne = zůstanou tam kde byly.
		/// Pokud bych nejprve odregistroval celý task a pak jej zaregistroval nově, měl bych výsledek 4-10 (sečtený čas), 
		/// a to v interaktivním režimu nechci.
		/// </summary>
		/// <param name="workPass">Jeden průchod</param>
		/// <param name="timeDir">Směr registrace času</param>
		/// <param name="startTime">Výchozí čas</param>
		/// <param name="limitedCType">Typ omezení kapacit pro přeregistraci</param>
		private bool _InterMoveOneOuterMovePass(WorkPassCls workPass, DateTime startTime, InteractiveAdjacentArgs adjArgs)
		{
			TimeRange.TimeDirection timeDir = adjArgs.TimeDir;
			LimitedCType limitedCType = adjArgs.LimitedCType;

			// Jak na to? To je snadné: vlastně budu napřímo pracovat jen s krajní položkou WorkTime (krajní v daném směru),
			// a pokud nebude vyhovovat, tak ji = tu jednu přeregistruji, jako by se s ní pohnulo ručně.
			// Návazné pohyby vyřeší interaktivní logika uvnitř jednoho tasku.
			DateTime outerDateTime;
			WorkTimeCls outerWorkTime = workPass.GetOuterWorkTime(timeDir, true, false, out outerDateTime);

			// Neexistuje žádný WorkTime: určím krajní okamžik z časů v WorkPass 
			//  (může jít o operaci s nulovou kapacitou - ale i tak bude třeba ji přesunout jinam, na vhodný čas, ale zvláštním způsobem):
			if (outerWorkTime == null)
				outerDateTime = workPass.WorkTimeRange.Edge(timeDir, true);

			// Pokud není konflikt mezi krajním časem tohoto průchodu a výchozím časem (parametr), mohu skončit:
			if (!adjArgs.IsMoveRequired(startTime, outerDateTime))
				return false;

			// Dojde k přeregistraci průchodu: 
			TimeRange workTime = workPass.WorkTimeRange;
			if (outerWorkTime != null)
			{	// Počínaje krajním časem, obdobně jako v interaktivním režimu:
				CapacityTimeRegData timeData = this._InterGetTimeDataWorkTime(outerWorkTime, timeDir, startTime, limitedCType);
				this._InterMoveOneInnerTime(workPass, timeData, adjArgs);
			}
			else
			{	// Průchod nemá časy, přeregistruje se celý = jen se korektně přesune na požadovaný čas:

			}

			// Nastala změna?
			bool isChange = (workPass.WorkTimeRange != workTime);
			return isChange;
		}
		/// <summary>
		/// Metoda, která zajistí odregistrování kapacit a nové zaregistrování kapacit pro dané položky WorkTime, v režimu Interactive.
		/// Vrací true, pokud došlo ke změně (odregistrování a/nebo zaregistrování kapacity).
		/// </summary>
		/// <param name="workPass">Data o průchodu (část tasku)</param>
		/// <param name="timeData">Data pro odregistrování a registraci kapacit</param>
		private bool _InterMoveOneTimeChange(WorkPassCls workPass, CapacityTimeRegData timeData)
		{
			if (timeData.UnregisterWorkTimeList == null)
				Throw.SysError(MessageInfo.Get("Změna registrace kapacit musí mít deklarované položky WorkTime, které má přemístit."));
			if (timeData.UnregisterWorkTimeList.Count == 0)
				Throw.SysError(MessageInfo.Get("Změna registrace kapacit musí mít deklarovanou alespoň jednu položku WorkTime, kterou má přemístit"));

			bool isChanged = false;
			try
			{
				workPass.TimeRegisterPrepare();                            // Příprava objektu na registraci kapacity "Po položkách WorkTime"

				// 1. Odregistrovat:
				foreach (WorkTimeCls workTime in timeData.UnregisterWorkTimeList)
				{
					// Najdeme WorkTime a odregistrujeme tento čas (všechny synchronní KPJ), tím se položka Time odebere z workPass:
					workPass.TimeRegisterFindUnregisteredIndex(workTime.TimeID);    // Najdu index odregistrovávané položky a připravím si jej pro následné vkládání nově registrovaných položek
					this.CapacityRegisterData.UnRegisterCapacity(workTime, this._InterRemoveWorkUnitHandler);
					isChanged = true;
				}

				// 2. Zaregistrovat kapacitu:
				if (timeData.Capacity > 0M)
				{
					this._InterMoveOneTimeChangeUnit(workPass, timeData);  // Připravit do workPass data pro změnu plánovacích jednotek podle zadání
					CapacityRegisterArgs regArgs = CapacityRegisterArgs.Create(this._InterAddWorkUnitHandler, this.InterWork);
					this.CapacityRegisterData.RegisterCapacityForTime(workPass, timeData, regArgs);
					isChanged = true;
				}
			}
			finally
			{
				workPass.TimeRegisterDone();
			}
			return isChanged;
		}
		/// <summary>
		/// Metoda zajistí předání požadavku na změnu KPJ z interaktivní vrstvy (activeItem) 
		/// do tasku (workPass), do seznamu explicitních požadavků workPass.UnitList.
		/// </summary>
		/// <param name="workPass">Pracovní průchod</param>
		/// <param name="timeData">Data o změně</param>
		private void _InterMoveOneTimeChangeUnit(WorkPassCls workPass, CapacityTimeRegData timeData)
		{
			if (timeData.CapacityUnitAssign == timeData.CapacityUnitExclude) return;     // Žádná změna.

			// Pokud některá ze zdejších KPJ je již uvedená v seznamu ve WorkPass (od minulé změny), tak ji tam odsud odeberu:
			workPass.UnitListRemove(timeData.CapacityUnitExclude);
			workPass.UnitListRemove(timeData.CapacityUnitAssign);

			// Zdejší interaktivní požadavky přidám do WorkPass:
			workPass.UnitListAddItem(timeData.GetUnitChangeItem());
		}
		/// <summary>
		/// Metoda, která zajistí zkontrolování návaznosti mezi jednotlivými položkami WorkTime mezi sebou.
		/// Metoda začne kontrolovat návaznost počínaje daným indexem WorkTime v daném směru.
		/// V případě problémů si zajistí přeregistrování kapacit.
		/// Funguje pouze v režimu "Tlak" = řeší konflikty typu Překrývání a Možné sloučení.
		/// Neřeší posun v režimu "Tah" = když posunu element X z času 10 do 12 (ToFuture), 
		///   tak nebudu přeregistrovávat element Y ve směru Previous z času 9 na 11.
		/// Zůstanou tam časové mezery mezi položkami.
		/// </summary>
		/// <param name="workPass">Pracovní průchod</param>
		/// <param name="index">Index položky času, kde má začít kontrola v daném směru</param>
		/// <param name="adjArgs">Parametry pro řízení přeregistrace času</param>
		/// <param name="isChange">In/Out: střadač provedené změny na aktuálním workPass. Změna nahodí na true.</param>
		private void _InterMoveOneInnerPush(WorkPassCls workPass, int index, InteractiveAdjacentArgs adjArgs, ref bool isChange)
		{
			int indexNext = index;
			int maxLoops = workPass.WorkTimeLst.Count * 3 + 7;
			while (true)
			{
				if (indexNext < 0 || indexNext >= workPass.WorkTimeLst.Count) break;   // Index mimo pole: konec.

				// TimeOut:
				if (maxLoops-- < 0)
				{
					Steward.Diagnostic.InfoToClipboard();
					Throw.SysError(MessageInfo.Get("Chyba: nepodařilo se vyřešit korekci položek WorkTime v předpokládaném počtu cyklů."));
				}

				// Určíme položky WorkTime v rámci tohoto WorkPass, které jsou v konfliktu s položkou na pozici indexNext:
				//    qqq tady se musí navíc zohlednit i režim Pull:
				CapacityTimeRegData timeData = workPass.TimeRegisterResultCheck(indexNext, adjArgs);
				if (timeData == null) break;                      // Žádný problém.

				// Diagnostika:
                workPass.AddToDiagnostic(this, "Obsah WorkTimeList před korekcí", "");
                timeData.AddToDiagnostic(this, "Korekce Push WorkTime", "");

				// Přeregistrujeme kapacity:
				bool oneChange = this._InterMoveOneTimeChange(workPass, timeData);
				if (oneChange && !isChange) isChange = true;      // Střadač příznaku změny na aktuálním workPass

				if (timeData.Capacity <= 0M) break;               // Žádná nová kapacita? Skončíme:

				// Určíme index poslední nově zaregistrované položky pro kontrolu:
				indexNext = workPass.TimeRegisterInsertGetIndex(adjArgs.TimeDir);
			}
		}
		#endregion
		#region PODPORA PRO INTERAKTIVNÍ HLEDÁNÍ KAPACIT - VYHLEDÁNÍ CELÉ LINIE WorkUnitId, SPOLUPRÁCE S CapacityDataCls
		/// <summary>
		/// Soupis klíčů WorkID pro ty WorkItem, které je možno přeregistrovat.
		/// Při iniciaci režimu "interaktivní přeregistrace" je Dictionary naplněna soupisem WorkID všech položek, 
		/// které jsou v nějakém vztahu k editované položce.
		/// V průběhu registrace kapacit se aktualizuje seznam i hodnoty v něm.
		/// Seznam se předává do systému registrace kapacit, kde dokáže identifikovat položky, které "nepřekáží" přeregistraci.
		/// Důvod: pokud mám dvě položky WorkUnit za sebou, a jednu z nich přesunu "na tu druhou", pak se ta druhá označí "k odregistrování"
		/// </summary>
		internal CapacityInterWorkCls InterWork { get; set; }
		/// <summary>
		/// Připraví soupis všech WorkUnitID, které nějak souvisejí s daným taskem.
		/// A to jak uvnitř daného Pass, tak všechny navazující operace, i operace navazující přes záznamy na ose S.
		/// Připraví tak seznam položek, které mohou být při přeregistraci kapacit jednoho WorkTime obcházeny, jako by nebyly.
		/// Jinými slovy: pokud mám operaci A v čase 10-12, na ni navazuje operace B v čase 12-18 (na stejné kapacitní jednotce!)
		/// a interaktivně posouvám operaci A na nový čas 14-16 (kde nyní je operace B), pak:
		/// bez tohoto mechanismu by se volná kapacita pro posunutou operaci A našla až po dokončení operace B, tedy v čase např. 18-20,
		/// a návazně by se operace B přeregistrovala (kvůli časové kontinuitě) na čas 20-26, což nechceme.
		/// Cílem je posunout operaci A skutečně na čas 14-16, s tím že operace B nám nyní nepřekáží 
		/// (protože je "naše" a v případě potřeby dojde k její přeregistraci),
		/// a následně při kontrole časové kontinuity dojde ke změně operace B na čas 16-22.
		/// Seznam má klíč: WorkUnitID, a hodnotu: co se smí nebo má s daným WorkUnitID dělat.
		/// Se seznamem se pracuje v kapacitním systému, je předáván jako parametr jménem interWork,
		/// je uložen v instanci třídy CapacityTaskExtRegCls v proměnné _InterWorkDict (property: InterWorkDict).
		/// </summary>
		/// <param name="task"></param>
        private void _InteractiveMovePrepare(CapacityPlanWorkItemCls task)
        {
            GID workGId = GID.Empty;
            if (task != null)
            {
                DataPointerStr workPointer = task.GetOuterPointer(TimeRange.TimeDirection.ToFuture, false, true);
                workGId = workPointer.Element;
            }
            this._InteractiveMovePrepare(workGId);
        }
		/// <summary>
		/// Připraví soupis všech WorkUnitID, které nějak souvisejí s daným workUnit.
		/// A to jak uvnitř daného Pass, tak všechny navazující operace, i operace navazující přes záznamy na ose S.
		/// Připraví tak seznam položek, které mohou být při přeregistraci kapacit jednoho WorkTime obcházeny, jako by nebyly.
		/// Jinými slovy: pokud mám operaci A v čase 10-12, na ni navazuje operace B v čase 12-18 (na stejné kapacitní jednotce!)
		/// a interaktivně posouvám operaci A na nový čas 14-16 (kde nyní je operace B), pak:
		/// bez tohoto mechanismu by se volná kapacita pro posunutou operaci A našla až po dokončení operace B, tedy v čase např. 18-20,
		/// a návazně by se operace B přeregistrovala (kvůli časové kontinuitě) na čas 20-26, což nechceme.
		/// Cílem je posunout operaci A skutečně na čas 14-16, s tím že operace B nám nyní nepřekáží 
		/// (protože je "naše" a v případě potřeby dojde k její přeregistraci),
		/// a následně při kontrole časové kontinuity dojde ke změně operace B na čas 16-22.
		/// Seznam má klíč: WorkUnitID, a hodnotu: co se smí nebo má s daným WorkUnitID dělat.
		/// Se seznamem se pracuje v kapacitním systému, je předáván jako parametr jménem interWork,
		/// je uložen v instanci třídy CapacityTaskExtRegCls v proměnné _InterWorkDict (property: InterWorkDict).
		/// </summary>
		/// <param name="workUnit"></param>
		private void _InteractiveMovePrepare(WorkUnitCls workUnit)
		{
            GID workGId = (workUnit == null ? GID.Empty : workUnit.WorkGID);
            this._InteractiveMovePrepare(workGId);
		}
        /// <summary>
        /// Připraví soupis všech WorkUnitID, které nějak souvisejí s daným workUnit.GID.
        /// A to jak uvnitř daného Pass, tak všechny navazující operace, i operace navazující přes záznamy na ose S.
        /// Připraví tak seznam položek, které mohou být při přeregistraci kapacit jednoho WorkTime obcházeny, jako by nebyly.
        /// Jinými slovy: pokud mám operaci A v čase 10-12, na ni navazuje operace B v čase 12-18 (na stejné kapacitní jednotce!)
        /// a interaktivně posouvám operaci A na nový čas 14-16 (kde nyní je operace B), pak:
        /// bez tohoto mechanismu by se volná kapacita pro posunutou operaci A našla až po dokončení operace B, tedy v čase např. 18-20,
        /// a návazně by se operace B přeregistrovala (kvůli časové kontinuitě) na čas 20-26, což nechceme.
        /// Cílem je posunout operaci A skutečně na čas 14-16, s tím že operace B nám nyní nepřekáží 
        /// (protože je "naše" a v případě potřeby dojde k její přeregistraci),
        /// a následně při kontrole časové kontinuity dojde ke změně operace B na čas 16-22.
        /// Seznam má klíč: WorkUnitID, a hodnotu: co se smí nebo má s daným WorkUnitID dělat.
        /// Se seznamem se pracuje v kapacitním systému, je předáván jako parametr jménem interWork,
        /// je uložen v instanci třídy CapacityTaskExtRegCls v proměnné _InterWorkDict (property: InterWorkDict).
        /// </summary>
        /// <param name="workGId"></param>
        private void _InteractiveMovePrepare(GID workGId)
        {
            // Vždy nový seznam: vygeneruje se mu nové ID, 
            // které pomáhá při registraci kapacit detekovat starý / nový / žádný požadavek na konfliktní WorkID:
            this.InterWork = new CapacityInterWorkCls();

            if (!workGId.IsEmpty)
            {
                // Soupis všech vztahů okolo dané WorkUnit, převést na seznam sousedních WorkID:
                List<DataRelation> relations = this.GetWorkUnitRelations(workGId.RecordNumber, true, true);
                foreach (DataRelation relation in relations)
                {
                    this._InterMovePrepareAddOne(relation.Current);
                    this._InterMovePrepareAddOne(relation.Other);
                }
            }

            // Finalizovat = uzavřít:
            this.InterWork.IsFinalized = true;
        }
        /// <summary>
		/// Do soupisu _InterWorkDict přidá další číslo WorkUnitID, pokud je na vstupu uvedeno, a v seznamu dosud není.
		/// </summary>
		/// <param name="dataPointer"></param>
		private void _InterMovePrepareAddOne(DataPointerStr dataPointer)
		{
			if (!dataPointer.IsEmpty && !dataPointer.Element.IsEmpty && dataPointer.Element.ClassNumber == Constants.ClassNumberWork)
			{
				this.InterWork.AddWorkID(dataPointer.Element.RecordNumber, InterWorkConflictState.AllowMovement);
			}
		}
		/// <summary>
		/// Ukončení a úklid po změně kapacit jednoho celého zadání (celý strom).
		/// </summary>
		private void _InteractiveMoveDone()
		{
			this.InterWork = null;
		}
		#endregion
        #region PODPORA PRO AUTOMATICKÉ PŘEPLÁNOVÁNÍ LINKOVANÝCH OPERACÍ - PŘI POHYBU JEDNÉ OPERACE Z LINKGROUP ZAJISTÍ VHODNÉ PŘEPLÁNOVÁNÍ I OSTATNÍCH OPERACÍ VE SKUPINĚ
        /// <summary>
        /// Připraví data pro automatické přeplánování linkovaných operací.
        /// </summary>
        /// <param name="autoMoveLinkedTaskMode"></param>
        private void _InterMoveAutoLinkPrepare(AutoMoveLinkedTaskMode moveMode)
        {
            this._InterMoveLinkInfo = new InterMoveLinkInfo(moveMode);
        }
        /// <summary>
        /// Zařadí daný task do interního přehledu tasků, pro které se řeší Auto-Move
        /// </summary>
        /// <param name="taskGId">GID tasku</param>
        /// <param name="asActive"></param>
        private void _InterMoveAutoLinkContact(GID taskGId, bool asActive)
        {
            if (!this._InterMoveLinkIsActive) return;
            CapacityPlanWorkItemCls task = this.FindTaskC(taskGId);
            this._InterMoveAutoLinkContact(task, asActive);
        }
        /// <summary>
        /// Zařadí daný task do interního přehledu tasků, pro které se řeší Auto-Move
        /// </summary>
        /// <param name="task">Data tasku</param>
        /// <param name="asActive">true = jde o aktivní task / false = jde o návazný task</param>
        private void _InterMoveAutoLinkContact(CapacityPlanWorkItemCls task, bool asActive)
        {
            if (task == null) return;
            if (task.LinkGroup == 0) return;                                          // Pokud daný task není v žádné skupině, pak do našeho soupisu nepatří.
            if (!this._InterMoveLinkIsActive) return;                                 // Pokud neřeším Auto-Move linkovaných tasků, skončím.
            InterMoveLinkTaskInfo taskInfo = this._InterMoveLinkInfo.GetTask(task);
            if (taskInfo == null)
                this._InterMoveAutoLinkAddTasks(task, asActive);                      // Task neznáme => přidáme jej (a všechny jeho sousedy)
            else
                taskInfo.SetActivity(asActive, !asActive);                            //  pak do něj vepíšeme aktivitu (buď je aktivní, nebo je adjacent; ale v této metodě je vždy externí)
        }
        /// <summary>
        /// Do interního přehledu tasků přidá daný task (jako aktivní) a jeho linkované sousedy
        /// </summary>
        /// <param name="task"></param>
        /// <param name="asActive">true = jeden z aktivních tasků / false = návazný task v řadě Prev/Next</param>
        private void _InterMoveAutoLinkAddTasks(CapacityPlanWorkItemCls task, bool asActive)
        {
            List<CapacityPlanWorkItemCls> taskList = this.FindTaskCLinkedList(task.LinkGroup);
            foreach (CapacityPlanWorkItemCls taskOne in taskList)
            {
                bool isExternal = (taskOne.TaskID == task.TaskID);          // true = Tento task (taskOne) je ten, který byl předán do vstupu této metody (externí) / false = jde o některý linkovaný task
                this._InterMoveLinkInfo.AddTask(taskOne, (isExternal && asActive), (isExternal && !asActive));
            }
        }
        /// <summary>
        /// Provede automatické přeplánování linkovaných operací.
        /// </summary>
        private void _InterMoveAutoLinkRun()
        {
            if (!this._InterMoveLinkIsActive) return;

            while (true)
            {   // Získám seznam tasků, které je třeba posunout, včetně informace o potřebném vektoru posunutí:
                List<InterMoveLinkTaskInfo> linkTaskList = this._InterMoveLinkInfo.GetLinkedTaskToMove();
                if (linkTaskList == null || linkTaskList.Count == 0)
                    break;

                // Tyto tasky budu posouvat v rámci hladiny Current+1:
                this._InterMoveLinkInfo.CurrentLevel++;
                foreach (InterMoveLinkTaskInfo linkTask in linkTaskList)
                    this._InterMoveAutoLinkRunOneTask(linkTask);
            }
        }
        /// <summary>
        /// Zajistí posunutí jednoho tasku podle daných údajů, jde o task v linkované skupině.
        /// Zajistí i posunutí souvisejících záznamů.
        /// </summary>
        /// <param name="linkTask"></param>
        private void _InterMoveAutoLinkRunOneTask(InterMoveLinkTaskInfo linkTask)
        {
            this._InterMoveOneTaskByTime(linkTask.Task, linkTask.LinkTimeVector, false);
        }
        /// <summary>
        /// Uvolní data pro automatické přeplánování linkovaných operací.
        /// Neřeší žádnou aktivitu, jen zahodí objekt this._InterMoveLinkInfo
        /// </summary>
        private void _InterMoveAutoLinkDone()
        {
            if (this._InterMoveLinkInfo != null)
                ((IDisposable)this._InterMoveLinkInfo).Dispose();
            this._InterMoveLinkInfo = null;
        }
        /// <summary>
        /// Příznak, zda je povolené (true) / není (false) dotahování operací na základě režimu MoveAutoLink.
        /// Pokud je false, pak se neprovádí přeregistrace operací (tasků) ve směru Pull = přitáhnout do volného místa (jako vagonky vlaku).
        /// Běžně je true.
        /// Jakmile jsou ale v režimu interaktivní registrace kapacit dokončeny přesuny "požadované", pak se řeší automatické přesuny linkovaných operací, 
        /// a v této fázi algoritmu interaktivní přeregistrace se ze směru Pull vynechávají ty tasky, které už jednou přeregistrovány byly.
        /// </summary>
        private bool _InterMoveAutoLinkEnabledPull(CapacityPlanWorkItemCls moveTask)
        {
            if (!this._InterMoveLinkIsActive) return true;                  // Pokud není režim MoveAutoLink aktivní, pak Pull povolím vždy bez problémů.
            if (this._InterMoveLinkInfo.CurrentLevel == 0) return true;     // Pokud ještě nejsme v režimu posouvání linkovaných tasků, pak dovolujeme přemisťovat ve směru Pull jakýkoli task.

            // V režimu 2 se ve směru Pull (přitahovat) mohou pohybovat jen tasky, které nejsou zaregistrovány v rámci nějaké již dříve zpracované hladiny:
            InterMoveLinkTaskInfo taskInfo = this._InterMoveLinkInfo.GetTask(moveTask);
            if (taskInfo == null) return true;                              // Tento task neznáme => můžeme jej posouvat i ve směru Pull.

            if (taskInfo.Level < this._InterMoveLinkInfo.CurrentLevel) return false;   // Tento task jsme dělali v některé z již uzavřených hladin => nelze pohybovat ve směru Pull !

            return true;
        }
        /// <summary>
        /// <para>
        /// Obsahuje true, pokud je aktivní režim interaktivní přeregistrace linkovaných tasků (metody this._InterMoveAutoLink*())
        /// </para>
        /// <para><i>
        /// To je tehdy, když je připravena podpora (objekt this._InterMoveLinkInfo), a je nastaven režim interaktivních přesunů MoveWhenSynchronized nebo MoveAlways.</i>
        /// </para>
        /// </summary>
        private bool _InterMoveLinkIsActive
        {
            get 
            {
                if (this._InterMoveLinkInfo == null) return false;
                if (this._InterMoveLinkInfo.AutoMoveMode == AutoMoveLinkedTaskMode.MoveWhenSynchronized || this._InterMoveLinkInfo.AutoMoveMode == AutoMoveLinkedTaskMode.MoveAlways) return true;
                return false;
            }
        }
        /// <summary>
        /// Data pro řízení pohybu linkovaných operací při interaktivním přeplánování.
        /// Typicky == null.
        /// </summary>
        private InterMoveLinkInfo _InterMoveLinkInfo;
        #region SADA TŘÍD PRO UKLÁDÁNÍ DAT PRO AUTOMATICKÉ PŘEPLÁNOVÁNÍ LINKOVANÝCH OPERACÍ
        /// <summary>
        /// Souhrnná data o všech skupinách (grupy), které se nějak účastní pohybu (grupy, tasky, jejich aktivita, jejich časy)
        /// </summary>
        protected class InterMoveLinkInfo : IDisposable
        {
            internal InterMoveLinkInfo(AutoMoveLinkedTaskMode moveMode)
            {
                this.AutoMoveMode = moveMode;
                this.CurrentLevel = 0;
                this.GroupDict = new Dictionary<int, InterMoveLinkGroupInfo>();
                this.TaskGroupIndex = new Dictionary<int, InterMoveLinkTaskInfo>();
            }
            /// <summary>
            /// Režim pohybu linkovaných operací
            /// </summary>
            internal AutoMoveLinkedTaskMode AutoMoveMode { get; private set; }
            /// <summary>
            /// Aktuální hladina zpracování tasků (aktivita uživatele = 0, další vrstvy jsou kladné, více viz metoda _InterMoveAutoLinkRun()
            /// </summary>
            internal int CurrentLevel { get; set; }
            /// <summary>
            /// Grupy: Key = číslo LinkGroup, Value = data této skupiny
            /// </summary>
            internal Dictionary<int, InterMoveLinkGroupInfo> GroupDict { get; private set; }
            /// <summary>
            /// Index tasků: Key = ID tasku, Value = data tohoto tasku, která jsou uložena v GroupDict[task.group].TaskDict[taskID] (jde o identický objekt)
            /// </summary>
            internal Dictionary<int, InterMoveLinkTaskInfo> TaskGroupIndex { get; private set; }
            /// <summary>
            /// Obsahuje this odkaz na daný task?
            /// </summary>
            /// <param name="task"></param>
            /// <param name="asActive"></param>
            /// <returns></returns>
            internal bool ContainsTask(CapacityPlanWorkItemCls task)
            {
                InterMoveLinkTaskInfo taskInfo;
                if (!this.TaskGroupIndex.TryGetValue(task.TaskID, out taskInfo)) return false;       // Daný task neznáme.
                return true;
            }
            /// <summary>
            /// Vrátí daný task (anebo null)
            /// </summary>
            /// <param name="task"></param>
            /// <returns></returns>
            internal InterMoveLinkTaskInfo GetTask(CapacityPlanWorkItemCls task)
            {
                InterMoveLinkTaskInfo taskInfo;
                if (!this.TaskGroupIndex.TryGetValue(task.TaskID, out taskInfo)) return null;
                return taskInfo;
            }
            /// <summary>
            /// Zajistí přidání daného tasku do svého seznamu, a nastavení příznaků.
            /// </summary>
            /// <param name="task"></param>
            /// <param name="isActive"></param>
            /// <param name="isAdjacent"></param>
            internal void AddTask(CapacityPlanWorkItemCls task, bool isActive, bool isAdjacent)
            {
                if (task == null || task.LinkGroup == 0) return;

                // Grupa
                InterMoveLinkGroupInfo groupInfo;
                if (!this.GroupDict.TryGetValue(task.LinkGroup, out groupInfo))
                {   // Nemáme zavedeno číslo skupiny => zaveďme jej:
                    groupInfo = new InterMoveLinkGroupInfo(task.LinkGroup, this.CurrentLevel);
                    this.GroupDict.Add(task.LinkGroup, groupInfo);
                }

                // Task v grupě
                InterMoveLinkTaskInfo taskInfo;
                if (!groupInfo.TaskDict.TryGetValue(task.TaskID, out taskInfo))
                {   // Ve skupině není task => zaveďme jej:
                    taskInfo = new InterMoveLinkTaskInfo(task, this.CurrentLevel);
                    groupInfo.TaskDict.Add(task.TaskID, taskInfo);
                }

                // Task v indexu:
                if (!this.TaskGroupIndex.ContainsKey(task.TaskID))
                    this.TaskGroupIndex.Add(task.TaskID, taskInfo);
                else
                    this.TaskGroupIndex[task.TaskID] = taskInfo;

                // Příznaky (nastavuji vždy, i když objekt už existoval):
                taskInfo.SetActivity(isActive, isAdjacent);
            }
            /// <summary>
            /// IDisposable.Dispose()
            /// </summary>
            void IDisposable.Dispose()
            {
                if (this.GroupDict != null)
                    this.GroupDict.Clear();
                if (this.TaskGroupIndex != null)
                    this.TaskGroupIndex.Clear();
            }
            #region URČENÍ ZÁVISLÝCH TASKŮ V RÁMCI GRUPY A URČENÍ ČASU A SMĚRU PRO JEJICH PŘEREGISTRACI
            /// <summary>
            /// Metoda najde všechny tasky, které je třeba posunout (podle režimu a podle času jejich sousedních tasků).
            /// Hledá pouze ty tasky (grupy), které jsou v hladině this.CurrentLevel.
            /// Výchozí hladina = 0 (tam posouvá uživatel), do této hladiny se vkládají grupy přímo ovlivněné uživatelem.
            /// Po dokončení přeregistrace této hladiny se vysbírají grupované tasky z této hladiny a zjistí se, zda je třeba některý z nich přesunout.
            /// Neřeší se tasky z předešlých (ani z budoucích) hladin.
            /// </summary>
            /// <returns></returns>
            internal List<InterMoveLinkTaskInfo> GetLinkedTaskToMove()
            {
                List<InterMoveLinkTaskInfo> result = new List<InterMoveLinkTaskInfo>();
                foreach (InterMoveLinkGroupInfo groupInfo in this.GroupDict.Values)
                {
                    // Pokud je grupa z jiné hladiny, neřeším ji:
                    if (groupInfo.Level != this.CurrentLevel) continue;

                    // Grupa sama ať si najde svoje tasky, a přidá je do resultu:
                    groupInfo.GetLinkedTaskToMove(this.AutoMoveMode, result);
                }
                return result;
            }
            #endregion
        }
        /// <summary>
        /// Data o všech operacích jedné grupy, z hlediska interaktivního přesunu
        /// </summary>
        protected class InterMoveLinkGroupInfo
        {
            internal InterMoveLinkGroupInfo(int groupLink, int level)
            {
                this.GroupLink = groupLink;
                this.TaskDict = new Dictionary<int, InterMoveLinkTaskInfo>();
                this.Level = level;
            }
            /// <summary>
            /// Číslo grupy
            /// </summary>
            internal int GroupLink { get; private set; }
            /// <summary>
            /// Hladina zpracování (aktivita uživatele = 0, další vrstvy jsou kladné, více viz metoda _InterMoveAutoLinkRun()
            /// </summary>
            internal int Level { get; private set; }
            /// <summary>
            /// Soupis tasků v grupě, a jejich dat (nejen data tasku, ale i časy a důvody pohybu).
            /// Key = TaskID, Value = data tasku a pohybu
            /// </summary>
            internal Dictionary<int, InterMoveLinkTaskInfo> TaskDict { get; set; }
            /// <summary>
            /// Souhrnný čas TimeOriginal všech tasků (součet = Min(Begin), Max(End))
            /// </summary>
            internal TimeRange TimeOriginal
            {
                get
                {
                    TimeRange result = TimeRange.Empty;
                    foreach (InterMoveLinkTaskInfo taskInfo in TaskDict.Values)
                        result = result + taskInfo.TimeOriginal;
                    
                    return result;
                }
            }
            /// <summary>
            /// Metoda najde všechny tasky, které je třeba v této grupě linkovaných operací posunout (podle režimu a podle času jejich sousedních tasků).
            /// </summary>
            /// <param name="moveMode">Režim pohybu</param>
            /// <param name="result"></param>
            internal void GetLinkedTaskToMove(AutoMoveLinkedTaskMode moveMode, List<InterMoveLinkTaskInfo> result)
            {
                switch (moveMode)
                {
                    case AutoMoveLinkedTaskMode.MoveWhenSynchronized:
                        this._GetLinkedTaskToMoveSynchronized(result);
                        break;
                    case AutoMoveLinkedTaskMode.MoveAlways:
                        this._GetLinkedTaskToMoveAlways(result);
                        break;
                }
            }
            /// <summary>
            /// Metoda najde všechny tasky, které je třeba v této grupě linkovaných operací posunout v režimu MoveWhenSynchronized
            /// </summary>
            /// <param name="result"></param>
            private void _GetLinkedTaskToMoveSynchronized(List<InterMoveLinkTaskInfo> result)
            {
                List<InterMoveLinkTaskInfo> moveList = this.TaskDict.Values.Where(ti => !ti.IsLinked).ToList();                // Soupis tasků, které nejsou linkované (jsou aktivní nebo adjacent)
                IEnumerable<InterMoveLinkTaskInfo> linkList = this.TaskDict.Values.Where(ti => ti.IsLinked && !ti.IsMoved);    // Soupis linkovaných tasků, které ještě nebyly posunuty
                foreach (InterMoveLinkTaskInfo linkTask in linkList)
                {   // Pro tento linkovaný task najdeme okolní již přesunuté tasky, a pokud byly původně synchronní, tak určíme nový čas přeregistraci pro linkTask:
                    TimeVector linkVector = TimeVector.Empty;
                    foreach (InterMoveLinkTaskInfo moveTask in moveList)
                    {
                        linkVector = InterMoveLinkTaskInfo.GetVectorIfSynchronized(linkTask, moveTask);
                        if (!linkVector.IsEmpty)
                            break;
                    }

                    // Máme určený vektor pro přeregistraci? A task je nyní registrován k jinému času, než udává požadovaný vektor?
                    if (!linkVector.IsEmpty && !linkTask.Task.TimeWorkMatchTimeVector(linkVector))
                    {
                        linkTask.LinkTimeVector = linkVector;
                        result.Add(linkTask);
                        break;
                    }
                }
            }
            /// <summary>
            /// Metoda najde všechny tasky, které je třeba v této grupě linkovaných operací posunout v režimu MoveAlways
            /// </summary>
            /// <param name="result"></param>
            private void _GetLinkedTaskToMoveAlways(List<InterMoveLinkTaskInfo> result)
            {
                List<InterMoveLinkTaskInfo> moveList = this.TaskDict.Values.Where(ti => !ti.IsLinked).ToList();                // Soupis tasků, které nejsou linkované (jsou aktivní nebo adjacent)

                // Určíme časové okno pro tasky "moveList" (ty, co už jsou posunuté) - a to jak původní, tak po přeregistraci:
                TimeRange moveGroupTimeOriginal = TimeRange.Empty;
                TimeRange moveGroupTimeResultant = TimeRange.Empty;

                IEnumerable<InterMoveLinkTaskInfo> linkList = this.TaskDict.Values.Where(ti => ti.IsLinked && !ti.IsMoved);    // Soupis linkovaných tasků, které ještě nebyly posunuty
                foreach (InterMoveLinkTaskInfo linkTask in linkList)
                {   // Pro tento linkovaný task najdeme okolní již přesunuté tasky, a pokud byly původně synchronní, tak určíme nový čas přeregistraci pro linkTask:
                    TimeVector linkVector = TimeVector.Empty;
                    // Nejprve bych radši našel vektor synchronní:
                    foreach (InterMoveLinkTaskInfo moveTask in moveList)
                    {
                        linkVector = InterMoveLinkTaskInfo.GetVectorIfSynchronized(linkTask, moveTask);
                        if (!linkVector.IsEmpty)
                            break;
                    }

                    // Pokud jsme nenašli synchronní čas, určíme čas sumární:
                    if (linkVector.IsEmpty)
                    {
                        // Pokud jsme dosud neurčili sumární časy move. udělám to teď:
                        if (moveGroupTimeOriginal.IsEmpty && moveGroupTimeResultant.IsEmpty)
                        {
                            foreach (InterMoveLinkTaskInfo moveTask in moveList)
                            {
                                moveGroupTimeOriginal += moveTask.TimeOriginal;
                                moveGroupTimeResultant += moveTask.TimeResultant;
                            }
                        }

                        linkVector = InterMoveLinkTaskInfo.GetVectorGroup(linkTask, moveGroupTimeOriginal, moveGroupTimeResultant);
                    }

                    // Máme určený vektor pro přeregistraci? A task je registrován k jinému času, než udává vektor?
                    if (!linkVector.IsEmpty && !linkTask.Task.TimeWorkMatchTimeVector(linkVector))
                    {
                        linkTask.LinkTimeVector = linkVector;
                        result.Add(linkTask);
                        break;
                    }
                }
            }
        }
        /// <summary>
        /// Data o jednom tasku, z hlediska interaktivního přesunu
        /// </summary>
        protected class InterMoveLinkTaskInfo
        {
            internal InterMoveLinkTaskInfo(CapacityPlanWorkItemCls task, int level)
            {
                this.Task = task;
                this.TimeOriginal = task.TimeWork.Clone();    // Opíšu si čas originál, protože v průběhu posunů se bude hodnota měnit task.TimeWork, ale this.TimeOriginal zůstane beze změny
                this.LinkTimeVector = TimeVector.Empty;
                this.Level = level;
            }
            /// <summary>
            /// Data o tasku
            /// </summary>
            internal CapacityPlanWorkItemCls Task { get; private set; }
            /// <summary>
            /// Hladina zpracování (aktivita uživatele = 0, další vrstvy jsou kladné, více viz metoda _InterMoveAutoLinkRun()
            /// </summary>
            internal int Level { get; private set; }
            /// <summary>
            /// Čas tasku výchozí, před zahájením editace. Jde o kopii hodnoty this.Task.TimeWork, která je vyhodnocená v konstruktoru do paměti této instance. Čtení této hodnoty je časově nenáročné.
            /// </summary>
            internal TimeRange TimeOriginal { get; private set; }
            /// <summary>
            /// Jde o task, který je v rámci editace aktivní (ten, kterým uživatel cíleně pohybuje).
            /// </summary>
            internal bool IsActive { get; set; }
            /// <summary>
            /// Jde o task, který se pohybuje z důvodu pohybu jeho Prev/Next tasku.
            /// </summary>
            internal bool IsAdjacent { get; set; }
            /// <summary>
            /// Jde o task, kterým se nepohybuje ani aktivně, ani návazně = ten by se měl pohybovat synchronizovaně z důvodu linkované skupiny.
            /// Měl by si najít některý ze sousedních tasků, a přizpůsobit se jeho času.
            /// </summary>
            internal bool IsLinked { get { return (!this.IsActive && !this.IsAdjacent); } }
            /// <summary>
            /// Příznak, že tento task už je přesunut (aby se nepřesouval vícekrát, při zkřížených vazbách).
            /// </summary>
            internal bool IsMoved { get; set; }
            /// <summary>
            /// Časový vektor pro přeregistraci linkovaného tasku (platí pro ten task, který se bude pohybovat závisle na jiných - aktivnějších operacích)
            /// </summary>
            internal TimeVector LinkTimeVector { get; set; }
            /// <summary>
            /// Čas tasku výsledný = vždy aktuální hodnota this.Task.TimeWork. Čtení této hodnoty je časově NÁROČNÉ - vždy se nově počítá. Nepoužívat zbytečně častokrát za sebou.
            /// </summary>
            internal TimeRange TimeResultant { get { return this.Task.TimeWork; } }
            /// <summary>
            /// Nastaví do tasku jeho aktivitu (IsActive, IsAdjacent)
            /// </summary>
            /// <param name="isActive"></param>
            /// <param name="isAdjacent"></param>
            internal void SetActivity(bool isActive, bool isAdjacent)
            {
                if (isActive && !this.IsActive)
                {   // Zjistili jsme, že task je aktivní:
                    this.IsActive = true;
                    this.IsAdjacent = false;
                }
                else if (isAdjacent && !this.IsActive && !this.IsAdjacent)
                {   // Zjistili jsme, že task je návazný:
                    this.IsAdjacent = true;
                }
            }
            /// <summary>
            /// Metoda určí časový vektor pro přesunutí daného tasku (1: linkTask), pokud byl původně synchronní s jiným taskem (2: moveTask), který se už přesunul.
            /// </summary>
            /// <param name="linkTask">Task, který by se měl posunout</param>
            /// <param name="moveTask">Task, který už byl posunut, podle něj bychom se měli synchronizovat</param>
            /// <returns></returns>
            internal static TimeVector GetVectorIfSynchronized(InterMoveLinkTaskInfo linkTask, InterMoveLinkTaskInfo moveTask)
            {
                TimeVector linkVector = TimeVector.Empty;
                TimeRange moveTime = moveTask.TimeResultant;
                bool syncBegin = (moveTask.TimeOriginal.Begin == linkTask.TimeOriginal.Begin);   // Byly tasky původně synchronní na jejich Begin?
                bool syncEnd = (moveTask.TimeOriginal.End == linkTask.TimeOriginal.End);         // Byly tasky původně synchronní na jejich End?
                if (syncBegin && syncEnd)
                {   // Pokud najdu task, jehož původní celý čas (Begin i End) byl shodný s původním časem linkovaného tasku,
                    // pak ???
                    if (moveTime.Begin > moveTask.TimeOriginal.Begin)
                        // Pokud se task pohnul doprava (do bucoucnosti), pak linkovaný task posuneme Begin, ToFuture:
                        linkVector = new TimeVector(moveTime.Begin, TimeRange.TimeDirection.ToFuture);
                    else
                        // a naopak (End, ToHistory):
                        linkVector = new TimeVector(moveTime.End, TimeRange.TimeDirection.ToHistory);
                }
                else if (syncBegin)
                {   // Pokud najdu task, jehož původní začátek je shodný s původním začátkem linkovaného tasku, 
                    // pak náš linkovaný task přeregistrujeme tak, aby začínal společně se začátkem tohoto synchronního tasku:
                    linkVector = new TimeVector(moveTime.Begin, TimeRange.TimeDirection.ToFuture);
                }
                else if (syncEnd)
                {   // Pokud najdu task, jehož původní konec je shodný s původním koncem linkovaného tasku, 
                    // pak náš linkovaný task přeregistrujeme tak, aby končil společně s koncem tohoto synchronního tasku:
                    linkVector = new TimeVector(moveTime.End, TimeRange.TimeDirection.ToHistory);
                }
                return linkVector;
            }
            /// <summary>
            /// Metoda určí časový vektor pro přesunutí daného tasku (1: linkTask), na základě jeho vztahu k časovému oknu 
            /// </summary>
            /// <param name="linkTask"></param>
            /// <param name="moveGroupTimeOriginal"></param>
            /// <param name="moveGroupTimeResultant"></param>
            /// <returns></returns>
            internal static TimeVector GetVectorGroup(InterMoveLinkTaskInfo linkTask, TimeRange moveGroupTimeOriginal, TimeRange moveGroupTimeResultant)
            {
                // Pokud se čas moveTasků nezměnil, nebudu měnit ani čas linkTasku:
                if (moveGroupTimeOriginal.Begin == moveGroupTimeResultant.Begin)
                    return TimeVector.Empty;

                // Pokud se moveTasky posunuly doprava (výsledný Begin je vyšší, než originál Begin), pak posunu linkTask.Begin (ToFuture) o přiměřený časový interval:
                if (moveGroupTimeOriginal.Begin < moveGroupTimeResultant.Begin)
                    return new TimeVector(linkTask.TimeOriginal.Begin + (moveGroupTimeResultant.Begin - moveGroupTimeOriginal.Begin), TimeRange.TimeDirection.ToFuture);

                // Pokud se moveTasky posunuly doleva (výsledný Begin je menší, než originál Begin), pak posunu linkTask.End (ToHistory) o přiměřený časový interval:
                if (moveGroupTimeOriginal.Begin < moveGroupTimeResultant.Begin)
                    return new TimeVector(linkTask.TimeOriginal.End - (moveGroupTimeOriginal.Begin - moveGroupTimeResultant.Begin), TimeRange.TimeDirection.ToHistory);

                return TimeVector.Empty;
            }
        }
        #endregion
        #endregion
        #region POMOCNÉ METODY PRO PŘEPLÁNOVÁNÍ
        /// <summary>
		/// Vrátí text popisující KPJ do hlášek, ve formě "#key = refer: nazev".
		/// </summary>
		/// <param name="planUnitC"></param>
		/// <returns></returns>
		private string _GetCapacityUnitText(int planUnitC)
		{
			return this.CapacityData.GetCapacityUnitText(planUnitC, true);
		}
		/// <summary>
		/// Argumenty pro řízení interaktivního přesunu dat.
		/// Fyzicky se ukládají do obecného úložiště this._InterArgs.
		/// Zde jsou k dispozici přetypované na zdejší konkrétní typ (PlanningInteractiveMoveArgs).
		/// </summary>
		private PlanningInteractiveMoveArgs _InterMoveArgs
		{
			get { return (PlanningInteractiveMoveArgs)this._InterArgs; }
			set { this._InterArgs = value; }
		}
		#endregion
		#endregion
		#region INTERAKTIVNÍ PŘEPLÁNOVÁNÍ - OBECNÁ ROVINA - ZAJIŠTĚNÍ KOREKTNÍCH VZTAHŮ NA SOUSEDNÍ ZÁZNAMY (TASK, AXIS)
		/// <summary>
		/// Řídící metoda pro kontrolu návazných záznamů k záznamu daného Tasku / Axis (parametr fromGId), v daném směru.
		/// Tuto metodu je možno volat obecně při interaktivním přeplánování, využívá obecného argumentu pro registraci editovaných položek.
		/// Před jejím voláním je nutno zavolat 
		/// this._InteractiveMovePrepare() = příprava seznamu WorkID sousedních prvků, 
		///   tak aby při přeregistraci kapacit byly v systému kapacit "neviditelné",
		/// a po změně je třeba zavolat this._InteractiveMoveDone() = ukončení neviditelnosti.
		/// Metoda zajistí časovou kontinuitu všech záznamů, případně je přeregistruje na vyhovující časy.
		/// Změny se mimo jiné projevují i na skladové časové ose (vývoj zásoby), ale metoda nenavrhuje nové akce na zajištění.
		/// Důsledkem tedy může být nedostatečná zásoba materiálu/polotovarů, pokud je do výroby plánováno použít skladovou zásobu.
		/// Pokud je plánováno využít výroby komponent = polotovarů, pak tato podřízená výroba bude posunuta.
		/// </summary>
		/// <param name="fromGId">GID záznamu. jehož sousedy chceme zkontrolovat. Tento záznam je výchozí kámen, s tím se nehýbe.</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private void _InteractiveShiftAdjacent(GID fromGId, InteractiveAdjacentArgs adjArgs)
		{
			if (fromGId.IsEmpty)
				Throw.SysError(MessageInfo.Get("Pro kontrolu návaznosti nebyl zadán výchozí bod (FromGID)."));

			if (!(adjArgs.TimeDir == TimeRange.TimeDirection.ToFuture || adjArgs.TimeDir == TimeRange.TimeDirection.ToHistory))
				Throw.SysError(MessageInfo.Get("Chyba zadání: kontrolu návazných záznamů _InteractiveShiftAdjacent() lze provádět jen pro směr ToFuture nebo ToHistory, "));
			bool toFuture = (adjArgs.TimeDir == TimeRange.TimeDirection.ToFuture);

			Queue<GID> otherGIdQueue = new Queue<GID>();
			otherGIdQueue.Enqueue(fromGId);                        // Zařadíme ke kontrole výchozí GID
			while (otherGIdQueue.Count > 0)
			{
				GID itemGIdBase = otherGIdQueue.Dequeue();         // Přečteme si GID záznamu, jehož následníky (v daném směru timeDir!) najdeme a pak budeme jednotlivě kontrolovat:
				List<PlanningItemRelationCls> relList = this.AxisHeap.FindRelationOtherIDData(adjArgs.TimeDir, itemGIdBase);

				foreach (PlanningItemRelationCls relItem in relList)
				{
					GID otherGId = relItem.ItemGIDEdge(adjArgs.TimeDir);   // GID cílové strany vztahu
					if (otherGId.IsEmpty || otherGId.RecordNumber == 0) continue;        // Pokud cíl je "nikam", pak cíl nekontroluji (ani jeho návaznosti), jde o konečnou stanici v balancování.
					switch (relItem.Relation)
					{
						case PlanningItemRelationType.AxisBalance:
						case PlanningItemRelationType.AxisTransfer:
							// Balancování zásoby na ose v rámci skladu:
							// Převod zásoby z jednoho skladu na jiný, stejná kmenová karta:
							if (this._InterShiftAdjacentPushAxisInner(relItem, adjArgs))  // Zajistí kontrolu a případně posun sousedního záznamu osy S, zajistí invalidaci řádku grafu zobrazujícího osu
								otherGIdQueue.Enqueue(otherGId);
							break;
						case PlanningItemRelationType.TaskInner:
							// Sousední operace jednoho dílce:
							if (this._InterShiftAdjacentPushTaskInner(relItem, adjArgs))  // Zajistí kontrolu a případně posun sousední operace, zajistí invalidaci řádku grafu zobrazujícího osu
								otherGIdQueue.Enqueue(otherGId);
							break;
						case PlanningItemRelationType.AxisToTask:
							// Výdej materiálu do komponenty do operace:
							if (this._InterShiftAdjacentPushAxisTask(relItem, adjArgs))   // Zajistí kontrolu a případně posun sousední operace / záznamu podle výchozího záznamu a směru, zajistí invalidaci řádku grafu zobrazujícího osu
								otherGIdQueue.Enqueue(otherGId);
							break;
						case PlanningItemRelationType.TaskToAxisFinal:
						case PlanningItemRelationType.TaskToAxisByProductSuitable:
						case PlanningItemRelationType.TaskToAxisByProductDissonant:
							// Příjem dílce z výroby na sklad:
							if (this._InterShiftAdjacentPushTaskAxis(relItem, adjArgs))   // Zajistí kontrolu a případně posun sousední operace / záznamu podle výchozího záznamu a směru, zajistí invalidaci řádku grafu zobrazujícího osu
								otherGIdQueue.Enqueue(otherGId);
							break;
						default:
							// Ostatní vztahy jsou sekundární (tj. překlenují se přes více primárních vztahů) a proto je neřešíme.
							// Např. jde o vztah z finální operace podřízeného dílce, kdy přechází polotovar jako kompomnenta do operace vyššího dílce.
							break;
					}
				}
			}
		}
		/// <summary>
		/// Kontrola sousedního záznamu ve vztahu o výdeje z osy (Prev = Axis) do výrobní operace (Next = Task).
		/// Zajistí, že navazující záznam (v daném směru) bude mít odpovídající datum z předešlého záznamu, 
		/// a že jeho vlastní lhůta bude odpovídat nově nastavenému předávacímu datu, a nově určí druhou stranu lhůty.
		/// </summary>
		/// <param name="relItem">Data o vztahu, obsahují GIDy záznamů na obou stranách vztahu</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private bool _InterShiftAdjacentPushAxisTask(PlanningItemRelationCls relItem, InteractiveAdjacentArgs adjArgs)
		{
			// Pozor: vztah je nesymetrický: na jedné straně (Prev) je záznam typu Axis, na straně Next je záznam typu Task.
			// To až směr času (timeDir) určuje, který z nich je fixní, a který je se bude kontrolovat!!!
			GID axisGId = relItem.ItemGIDPrev;
			MaterialPlanAxisItemCls axis = this.AxisHeap.FindAxisSItem(axisGId);
			if (axis == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen podkladový záznam osy S (jeho klíč=%0).", axisGId.RecordNumber));

			GID taskGId = relItem.ItemGIDNext;
			CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(taskGId);
			if (task == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen cílový záznam Tasku (jeho klíč=%0).", taskGId.RecordNumber));

			// Podle směru času provedu volbu toho, co je base a co push, a rovnou vyvolám kontrolu a případnou změnu záznamu Push:
			bool isChange = false;
			DateTime baseEdge;
			switch (adjArgs.TimeDir)
			{
				case TimeRange.TimeDirection.ToFuture:
					// Směr ToFuture: základnou je Prev = Axis, kontroluji Next = Task, případně posouvám Task dle data:
					if (this._InterIsTimeConflict(adjArgs.TimeDir, axis.DatePlanTime, task.TimeWork, out baseEdge))
						isChange = this._InterMoveOneOuterMoveTask(task, baseEdge, adjArgs);
					break;
				case TimeRange.TimeDirection.ToHistory:
					// Směr ToHistory: základnou je Next = Task, kontroluji Prev = Axis, případně posouvám Axis dle data:
					if (this._InterIsTimeConflict(adjArgs.TimeDir, task.TimeWork, axis.DatePlanTime, out baseEdge))
						isChange = this._InterShiftAdjacentMoveAxisS(axis, baseEdge, adjArgs);
					break;
			}
			return isChange;
		}
		/// <summary>
		/// Kontrola sousedního záznamu ve vztahu o příjmu z operace (Prev = Task) na osu S (Next = Axis).
		/// Zajistí, že navazující záznam (v daném směru) bude mít odpovídající datum z předešlého záznamu, 
		/// a že jeho vlastní lhůta bude odpovídat nově nastavenému předávacímu datu, a nově určí druhou stranu lhůty.
		/// </summary>
		/// <param name="relItem">Data o vztahu, obsahují GIDy záznamů na obou stranách vztahu</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private bool _InterShiftAdjacentPushTaskAxis(PlanningItemRelationCls relItem, InteractiveAdjacentArgs adjArgs)
		{
			// Pozor: vztah je nesymetrický: na jedné straně (Prev) je záznam typu Task, na straně Next je záznam typu Axis.
			// To až směr času (timeDir) určuje, který z nich je fixní, a který je se bude kontrolovat!!!
			GID taskGId = relItem.ItemGIDPrev;
			CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(taskGId);
			if (task == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen podkladový záznam Tasku (jeho klíč=%0).", taskGId.RecordNumber));

			GID axisGId = relItem.ItemGIDNext;
			MaterialPlanAxisItemCls axis = this.AxisHeap.FindAxisSItem(axisGId);
			if (axis == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen cílový záznam osy S (jeho klíč=%0).", axisGId.RecordNumber));

			// Podle směru času provedu volbu toho, co je base a co push, a rovnou vyvolám kontrolu a případnou 
			DateTime baseEdge;
			bool isChange = false;
			switch (adjArgs.TimeDir)
			{
				case TimeRange.TimeDirection.ToFuture:
					// Směr ToFuture: základnou je Prev = Task, kontroluji Next = Axis, případně posouvám Axis dle data:
					// Poznámka 1: tento směr běžně nekontroluji, protože záznam osy S je záznamem o příjmu z výroby, 
					//   a takový záznam (jeho časy) je aktualizován průběžně při přeregistrování kapacity, 
					//   viz metoda this._InterRecalcAxisTimeFromTasks()
					// Poznámka 2: kontrola se provádí pouze u záznamů typu Příjem vedlejšího produktu, 
					//   protože tento typ záznamu není ovlivněn přepočtem dle poznámky 1 (ten přepočítává jen záznam Parent = příjem finálu).
					if (axis.ChangeType == PlanSAxisChangeType.IncrementByPlanByProductSuitable ||
						axis.ChangeType == PlanSAxisChangeType.IncrementByRealByProductSuitable)
					{
						if (this._InterIsTimeConflict(adjArgs.TimeDir, task.TimeWork, axis.DatePlanTime, out baseEdge))
							isChange = this._InterShiftAdjacentMoveAxisS(axis, baseEdge, adjArgs);
					}
					else
						// Jiné typy pohybů neřeším explicitně (jde o příjem z tasku na osu, a tam proběhla rekalkulace osy).
						// Proto nastavím IsChange = true, aby proběhla i kontrola návazných záznamů osy S i když tady ke změně už nedošlo:
						isChange = true;
					break;
				case TimeRange.TimeDirection.ToHistory:
					// Směr ToHistory: základnou je Next = Axis, kontroluji Prev = Task, případně posouvám Task dle data:
					if (this._InterIsTimeConflict(adjArgs.TimeDir, axis.DatePlanTime, task.TimeWork, out baseEdge))
						isChange = this._InterMoveOneOuterMoveTask(task, baseEdge, adjArgs);
					break;
			}
			return isChange;
		}
		/// <summary>
		/// Kontrola sousedního záznamu ve vztahu mezi dvěma záznamy osy S, a to buď ve vztahu AxisBalance nebo AxisTransfer.
		/// Zajistí, že navazující záznam (v daném směru) bude mít odpovídající datum z předešlého záznamu, 
		/// a že jeho vlastní lhůta bude odpovídat nově nastavenému předávacímu datu, a nově určí druhou stranu lhůty.
		/// </summary>
		/// <param name="relItem">Data o vztahu, obsahují GIDy záznamů na obou stranách vztahu</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private bool _InterShiftAdjacentPushAxisInner(PlanningItemRelationCls relItem, InteractiveAdjacentArgs adjArgs)
		{
			// Najdu axis Base (výchozí, pevný):
			GID baseAxisGId = relItem.ItemGIDEdge(adjArgs.TimeDir, true);    // GID předchozího prvku (ToFuture = Prev, ToHistory = Next)
			MaterialPlanAxisItemCls baseAxis = this.AxisHeap.FindAxisSItem(baseAxisGId);
			if (baseAxis == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen výchozí záznam osy S (jeho klíč=%0).", baseAxisGId.RecordNumber));

			// Najdu axis Push (ten, který budu posouvat):
			GID pushAxisGId = relItem.ItemGIDEdge(adjArgs.TimeDir);          // GID sousedního prvku (ToFuture = Next, ToHistory = Prev)
			MaterialPlanAxisItemCls pushAxis = this.AxisHeap.FindAxisSItem(pushAxisGId);
			if (pushAxis == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen navazující záznam osy S (jeho klíč=%0).", pushAxisGId.RecordNumber));

			// Zajistím kontrolu návaznosti ze základnového záznamu osy S na návazný záznam:
			bool isChange = false;
			DateTime baseEdge;
			if (this._InterIsTimeConflict(adjArgs.TimeDir, baseAxis.DatePlanTime, pushAxis.DatePlanTime, out baseEdge))
				isChange = this._InterShiftAdjacentMoveAxisS(pushAxis, baseEdge, adjArgs);
			else if (adjArgs.PullWhenPossible)
			{ }
			return isChange;
		}
		/// <summary>
		/// Kontrola sousedního záznamu ve vztahu InnerTask: zjistí, zda dvě sousední operace v rámci výroby jednoho dílce jsou časově navazující.
		/// Pokud kontrola určí, že následující operaci (task) je nutno přesunout, provede se to.
		/// </summary>
		/// <remarks>Význam směru času v této kontrole:
		/// Pokud je směr ToFuture, pak výchozím taskem je Prev, a kontrolovaným je Next.
		/// Pokud je směr ToHistory, pak výchozím taskem je Next, a kontrolovaným je Prev.
		/// </remarks>
		/// <param name="relItem">Data o vztahu, obsahují GIDy záznamů na obou stranách vztahu</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private bool _InterShiftAdjacentPushTaskInner(PlanningItemRelationCls relItem, InteractiveAdjacentArgs adjArgs)
		{
			// Najdu task Prev:
			GID baseTaskGId = relItem.ItemGIDEdge(adjArgs.TimeDir, true);    // GID předchozího prvku (ToFuture = Prev, ToHistory = Next)
			CapacityPlanWorkItemCls baseTask = this.AxisHeap.FindTaskCItem(baseTaskGId);
			if (baseTask == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen záznam podkladového Tasku (jeho klíč=%0).", baseTaskGId.RecordNumber));

			// Najdu task Next:
			GID pushTaskGId = relItem.ItemGIDEdge(adjArgs.TimeDir);          // GID sousedního prvku (ToFuture = Next, ToHistory = Prev)
			CapacityPlanWorkItemCls pushTask = this.AxisHeap.FindTaskCItem(pushTaskGId);
			if (pushTask == null)
				Throw.SysError(MessageInfo.Get("Při interaktivní změně registrace kapacit nebyl nalezen záznam navazujícího Tasku (jeho klíč=%0).", pushTaskGId.RecordNumber));

			// Pokud je task Next fixovaný [a task Prev nefixovaný ?], nebudu taskem Next pohybovat:
			if (pushTask.IsFixedTask)
				return false;

			// Určím, zda se bude task provádět koordinovaně s předešlým úkolem:
			bool isCoordinated = this.CapacityBatchControl.IsCoordinableWith(pushTask);
			bool isChange = false;
			if (!isCoordinated)
				isChange = this._InterShiftAdjacentCheckTaskDate(baseTask, pushTask, adjArgs);
			else
				isChange = this._InterShiftAdjacentCheckTaskBatch(baseTask, pushTask, adjArgs);

			// Zajistím přepočet sumárního času všech operací výroby jednoho dílce a jeho zápis na záznam osy S:
			this._InterRecalcAxisTimeFromTasks(pushTask.AxisGID);    // Ale výsledek této metody (příznak změny) me tady NEZAJÍMÁ !!! Tady detekuji změnu jednoho Tasku = operace, a to je výsledek metody _InterMoveOneOuterCheckTaskDate().
			return isChange;
		}
		/// <summary>
		/// Metoda zajistí přesunutí data počátku a konce na záznamu osy S podle požadavků interaktivního přeplánování a charakteru záznamu.
		/// Rovněž přidá GID řádku do argumentu _InterMoveArgs pro invalidaci řádku.
		/// </summary>
		/// <param name="axis">Záznam osy S</param>
		/// <param name="baseEdge">Výchozí čas (podle směru času: Begin pro ToFuture, nebo End pro ToHistory)</param>
		/// <param name="adjArgs">Parametry pro posun položek</param>
		private bool _InterShiftAdjacentMoveAxisS(MaterialPlanAxisItemCls axis, DateTime baseEdge, InteractiveAdjacentArgs adjArgs)
		{
			// Pokud tento záznam osy S nelze přesouvat (například fixovaný záznam)
			// qqq

			TimeRange axisTime = axis.DatePlanTime;

			// Zásah do záznamu osy S:
			axis.SetRequestTimeVector(baseEdge, adjArgs.TimeDir);        // Naplní časový vektor DatePlanRequest + DatePlanRequestDirection
			axis.TimeCalculate(baseEdge, adjArgs.TimeDir);               // Nastaví výchozí datum v daném směru, a určí koncové datum v tomto směru

			// Zásah na odpovídající skladovou kartu, do jejího vývoje, v okamžiku (příjem na konci, výdej na začátku):
			this.MaterialData.MoveSAxisToTime(axis, axis.DatePlanEnd);

			// Zajistíme refresh řádku, změnila se jeho data:
			this._InterArgs.AddChangedRow(axis.AxisGID);

			bool isChange = (axis.DatePlanTime != axisTime);
			return isChange;
		}
		/// <summary>
		/// Metoda zjistí, zda dvě operace (tasky) lze provádět pomocí koordinovaných výrobních dávek, nebo ne.
		/// </summary>
		/// <param name="task1">Operace 1 (task)</param>
		/// <param name="task2">Operace 2 (task)</param>
		/// <returns></returns>
		private bool _InterShiftAdjacentIsBatchCoordinated(CapacityPlanWorkItemCls task1, CapacityPlanWorkItemCls task2)
		{
			return (CapacityBatchTaskCls.IsTaskDivisibleToBatch(task1) && CapacityBatchTaskCls.IsTaskDivisibleToBatch(task2));
		}
		/// <summary>
		/// Kontrola sousedního záznamu ve vztahu InnerTask: zjistí, zda dvě sousední operace v rámci výroby jednoho dílce jsou časově navazující.
		/// Režim předávání rozpracovanosti mezi operacemi je klasický = celé množství dokončit a na další operaci začít až poté.
		/// Pokud kontrola určí, že následující operaci (task) je nutno přesunout, provede se to.
		/// </summary>
		/// <remarks>Směr kontroly:
		/// Pokud je směr ToFuture, pak prevTask je nižší (menší číslo operace) a otherTask je vyšší.
		/// Pokud je směr ToHistory, pak prevTask je vyšší (větší číslo operace) a otherTask je nižší.
		/// </remarks>
		/// <param name="baseTask">Task výchozí, ten měnit nebudeme, slouží jako základna pro kontrolu</param>
		/// <param name="moveTask">Task cílový, ten kontrolujeme a v případě potřeby přeregistrujeme</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private bool _InterShiftAdjacentCheckTaskDate(CapacityPlanWorkItemCls baseTask, CapacityPlanWorkItemCls moveTask, InteractiveAdjacentArgs adjArgs)
		{
			// Časy operací (task.TimeWork) jsou souhrnem časů jednotlivých průchodů workPass:
			bool isChange = false;
			DateTime baseEdge;
			// qqq tady je jak Push, tak Pull:
			if (this._InterIsTimeConflict(adjArgs.TimeDir, baseTask.TimeWork, moveTask.TimeWork, out baseEdge))
				isChange = this._InterMoveOneOuterMoveTask(moveTask, baseEdge, adjArgs);
            // Pokud je "přitahování" povoleno v argumentu, pokud není zakázané od režimu AutoLink, a pokud je nějaký čas, pak to zkusíme:
			else if (adjArgs.PullWhenPossible && this._InterMoveAutoLinkEnabledPull(moveTask) && this._InterIsTimeSpace(adjArgs.TimeDir, baseTask.TimeWork, moveTask.TimeWork, out baseEdge))
				isChange = this._InterMoveOneOuterMoveTask(moveTask, baseEdge, adjArgs);
			return isChange;
		}
		/// <summary>
		/// Kontrola sousedního záznamu v vztahu InnerTask: zjistí, zda dvě sousední operace v rámci výroby jednoho dílce jsou časově navazující.
		/// Režim předávání rozpracovanosti mezi operacemi je přes výrobní dávky = koordinovaný.
		/// Pokud kontrola určí, že následující operaci (task) je nutno přesunout, provede se to.
		/// </summary>
		/// <remarks>Směr kontroly:
		/// Pokud je směr ToFuture, pak prevTask je nižší (menší číslo operace) a otherTask je vyšší.
		/// Pokud je směr ToHistory, pak prevTask je vyšší (větší číslo operace) a otherTask je nižší.
		/// </remarks>
		/// <param name="baseTask">Task výchozí, ten měnit nebudeme, slouží jako základna pro kontrolu</param>
		/// <param name="pushTask">Task cílový, ten kontrolujeme a v případě potřeby přeregistrujeme</param>
		/// <param name="adjArgs">Parametry pro změnu registrovaného času</param>
		private bool _InterShiftAdjacentCheckTaskBatch(CapacityPlanWorkItemCls baseTask, CapacityPlanWorkItemCls pushTask, InteractiveAdjacentArgs adjArgs)
		{
			throw new NotImplementedException();

			/*

			Co nás tady čeká?
			1. Rekonstruovat výrobní dávky z časů WorkPass.WorkTimeLst, a vložit je do this.CapacityBatchControl
			     (rozšíření schopností třídy WorkPassCls, která naplní svoje data do objektu this.CapacityBatchControl, 
			      což nebude složité = vyvolají se metody TaskRegisterInit(), TaskRegisterAddTime(), TaskRegisterDone() )

			2. Detekovat čas, kde dochází k porušení koordinace předávání výrobních dávek 
			     (rozšíření schopností třídy CapacityBatchTaskCls, to bude zajímavější)

			3. Řízení interaktivních posunů uvnitř operace pushTask opatrným vyhledáním kolidujícího Time a jeho posunutím

			*/
		}
		/// <summary>
		/// Metoda přepočítá sumární čas na ose S ze všech přímých podřízených výrobních operací.
		/// Pokud daný záznam (GID) neodpovídá příjmu z výroby, tj. nemá podřízenou výrobní operaci (Task), pak se jeho čas nepřepočítává.
		/// Jinak se projdou všechny přímo podřízené záznamy Task a součet jejich časů se vepíše na záznam osy.
		/// </summary>
		/// <param name="axisGId">GID osy S</param>
		private bool _InterRecalcAxisTimeFromTasks(GID axisGId)
		{
			return this._InterRecalcAxisTimeFromTasks(axisGId, false);
		}
		/// <summary>
		/// Metoda přepočítá sumární čas na ose S ze všech přímých podřízených výrobních operací.
		/// Pokud daný záznam (GID) neodpovídá příjmu z výroby, tj. nemá podřízenou výrobní operaci (Task), pak se jeho čas nepřepočítává.
		/// Jinak se projdou všechny přímo podřízené záznamy Task a součet jejich časů se vepíše na záznam osy.
		/// </summary>
		/// <param name="axisGId">GID osy S</param>
		/// <param name="nullRequest">Požadavek (true) na nulování DateRequest a DateRequestDirection</param>
		private bool _InterRecalcAxisTimeFromTasks(GID axisGId, bool nullRequest)
		{
			MaterialPlanAxisItemCls axis = this.AxisHeap.FindAxisSItem(axisGId);
			if (axis == null)
				Throw.SysError(MessageInfo.Get("Při přepočtu času materiálové osy nebyl nalezen požadovaný záznam osy S %0.", axisGId.RecordNumber));

			return this._InterRecalcAxisTimeFromTasks(axis, nullRequest);
		}
		/// <summary>
		/// Metoda přepočítá sumární čas na ose S ze všech přímých podřízených výrobních operací.
		/// Pokud daný záznam (GID) neodpovídá příjmu z výroby, tj. nemá podřízenou výrobní operaci (Task), pak se jeho čas nepřepočítává.
		/// Jinak se projdou všechny přímo podřízené záznamy Task a součet jejich časů se vepíše na záznam osy.
		/// </summary>
		/// <param name="axis">Data osy S</param>
		private bool _InterRecalcAxisTimeFromTasks(MaterialPlanAxisItemCls axis)
		{
			return this._InterRecalcAxisTimeFromTasks(axis, false);
		}
		/// <summary>
		/// Metoda přepočítá sumární čas na ose S ze všech přímých podřízených výrobních operací.
		/// Pokud daný záznam (GID) neodpovídá příjmu z výroby, tj. nemá podřízenou výrobní operaci (Task), pak se jeho čas nepřepočítává.
		/// Jinak se projdou všechny přímo podřízené záznamy Task a součet jejich časů se vepíše na záznam osy.
		/// </summary>
		/// <param name="axis">Data osy S</param>
		/// <param name="nullRequest">Požadavek (true) na nulování DateRequest a DateRequestDirection</param>
		private bool _InterRecalcAxisTimeFromTasks(MaterialPlanAxisItemCls axis, bool nullRequest)
		{
			GID axisGId = axis.AxisGID;

			// Výchozí čas: čas požadavku (od kdy se má začít vyrábět), což je na ose zaznamenaný požadavek:
            TimeRange axisTime = TimeRange.Empty;
            if (axis.DatePlanRequest.HasValue)
                axisTime = new TimeRange(axis.DatePlanRequest.Value, axis.DatePlanRequest.Value);

			// Nulování requestu:
			if (nullRequest)
			{
				axis.DatePlanRequest = null;
				axis.DatePlanRequestDirection = TimeRange.TimeDirection.Stay;
				axisTime = TimeRange.Empty;
			}

			// Fronta požadavků na zpracování:
			Queue<GID> queueGId = new Queue<GID>();
			queueGId.Enqueue(axisGId);                   // Do fronty ke zpracování zařadím cílový záznam osy S

			// Najdu přímé podřízené tasky v jedné linii a sečtu jejich čas:
			while (queueGId.Count > 0)
			{
				GID targetGId = queueGId.Dequeue();      // Vyzvednu z fronty nejstarší záznam ke zpracování
				// Najdu vztah[y] z daného záznamu na předcházející (časově dřívější) záznamy, 
				//   typ: TaskToAxis (pro první GID, který je GIDem osy S)
				//  anebo TaskInner (pro všechny vztahy mezi operacemi jednoho dílce):
                List<PlanningItemRelationCls> relList = this.AxisHeap.FindRelationPrevIDData(targetGId, r => r.IsRelationInList(PlanningItemRelationType.TaskToAxisFinal, PlanningItemRelationType.TaskToAxisByProductSuitable, PlanningItemRelationType.TaskInner));

				// Pro všechny vztažené záznamy se podívám na jejich stranu Prev (časově dřívější):
				foreach (PlanningItemRelationCls relItem in relList)
				{
					GID prevGId = relItem.ItemGIDPrev;
					if (prevGId.ClassNumber == CapacityPlanWorkItemCls.ClassNr)
					{
						CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(prevGId);
						if (task != null)
						{
							axisTime = axisTime + task.TimeWork;   // Sumarizuji čas z jednotlivých operací
							queueGId.Enqueue(prevGId);   // Do fronty ke zpracování zařadím i právě zpracovaný task: v příštím cyklu se podívám i na jeho předchůdce.
						}
					}
				}
			}

			// Detekce změny, uložení nové změněné hodnoty:
			bool isChange = (axis.DatePlanTime != axisTime);
			if (isChange)
			{
				axis.SetAxisDateRange(axisTime);
				this._InterArgs.AddChangedRow(axis.AxisGID);
			}

			return isChange;
		}
		/// <summary>
		/// Metoda určí, zda z hlediska daného směru jsou dva časové intervaly v konfliktu, 
		/// a pokud ano tak nastaví out parametr baseEdge (vloží do něj koncový čas základny) a vrátí true.
		/// Pokud není konflikt, vrací false.
		/// <example>
		/// Příklad pro směr ToFuture : baseTime = {10-18}, pushTime = {16-20}: out baseEdge = 18, return = true
		///    (základna je pevná, končí v 18, kontrolní úsek začíná v 16, to je konflikt, má začínat v 18)
		/// Příklad pro směr ToHistory: baseTime = {16-20}, pushTime = {10-18}: out baseEdge = 16, return = true
		///    (základna je pevná, začíná v 16, kontrolní úsek končí v 18, to je konflikt, má končit v 16)
		/// Příklad pro směr ToFuture : baseTime = {10-12}, pushTime = {16-20}: out baseEdge = 12, return = false (není konflikt)
		/// </example>
		/// </summary>
		/// <param name="timeDir">Směr času</param>
		/// <param name="baseTime">Základnový časový interval</param>
		/// <param name="pushTime">Kontrolovaný interval</param>
		/// <param name="baseEdge">Out: koncový čas základny, což je výchozí čas pro přesun kontrolovaného intervalu</param>
		/// <returns></returns>
		private bool _InterIsTimeConflict(TimeRange.TimeDirection timeDir, TimeRange baseTime, TimeRange pushTime, out DateTime baseEdge)
		{
			baseEdge = baseTime.Edge(timeDir);                   // Kdy interval Base skončí (ToFuture = End, ToHistory = Begin)
			DateTime pushEdge = pushTime.Edge(timeDir, true);    // Kdy interval Push začne (ToFuture = Begin, ToHistory = End)

			if (baseEdge == DateTime.MinValue || pushEdge == DateTime.MinValue) return false;

			return ((timeDir == TimeRange.TimeDirection.ToFuture && pushEdge < baseEdge) || (timeDir == TimeRange.TimeDirection.ToHistory && pushEdge > baseEdge));
		}
		/// <summary>
		/// Metoda určí, zda z hlediska daného směru času je mezi dvěma časovými intervaly nějaký volný čas.
		/// Účelem je zjistit, zda by bylo možné interval (nextTime) přisunout blíže k intervalu baseTime.
		/// Pokud to není možné, vrací se false.
		/// </summary>
		/// <param name="timeDir">Směr času</param>
		/// <param name="baseTime">Základnový časový interval</param>
		/// <param name="nextTime">Kontrolovaný interval</param>
		/// <param name="baseEdge">Out: koncový čas základny, což je výchozí čas pro přesun kontrolovaného intervalu</param>
		/// <returns></returns>
		private bool _InterIsTimeSpace(TimeRange.TimeDirection timeDir, TimeRange baseTime, TimeRange nextTime, out DateTime baseEdge)
		{
			baseEdge = baseTime.Edge(timeDir);                   // Kdy interval Base skončí (ToFuture = End, ToHistory = Begin)
			DateTime nextEdge = nextTime.Edge(timeDir, true);    // Kdy interval Next začne (ToFuture = Begin, ToHistory = End)

			if (baseEdge == DateTime.MinValue || nextEdge == DateTime.MinValue) return false;

			return ((timeDir == TimeRange.TimeDirection.ToFuture && nextEdge > baseEdge) || (timeDir == TimeRange.TimeDirection.ToHistory && nextEdge < baseEdge));
		}
		/// <summary>
		/// Řídící data, procházející skrz metody interaktivních posunů
		/// </summary>
		internal class InteractiveAdjacentArgs
		{
			public InteractiveAdjacentArgs(TimeRange.TimeDirection timeDir, LimitedCType limitedCType, bool pullWhenPossible)
			{
				this.TimeDir = timeDir;
				this.LimitedCType = limitedCType;
				this.PullWhenPossible = pullWhenPossible;
			}

			internal static InteractiveAdjacentArgs CreateForTask(TimeRange.TimeDirection timeDir, PlanningInteractiveRegisterArgs regArgs)
			{
				return new InteractiveAdjacentArgs(timeDir, regArgs.CapacityLimitForActiveTask, regArgs.PullAdjacentForActiveTask);
			}

			internal static InteractiveAdjacentArgs CreateForTree(TimeRange.TimeDirection timeDir, PlanningInteractiveRegisterArgs regArgs)
			{
				return new InteractiveAdjacentArgs(timeDir, regArgs.CapacityLimitForActiveTree, regArgs.PullAdjacentForActiveTree);
			}
			public TimeRange.TimeDirection TimeDir { get; set; }
			public LimitedCType LimitedCType { get; set; }
			public bool PullWhenPossible { get; set; }
			/// <summary>
			/// Vrací true, když je za daných okolností třeba přesunout čas daného záznamu.
			/// </summary>
			/// <param name="startTime"></param>
			/// <param name="currentTime"></param>
			/// <returns></returns>
			internal bool IsMoveRequired(DateTime startTime, DateTime currentTime)
			{
				switch (this.TimeDir)
				{
					case TimeRange.TimeDirection.ToFuture:
						// Do budoucnosti:
						if (currentTime < startTime)           // Aktuální čas je před požadovaným => je třeba aktuální posunout
							return true;
						if (currentTime == startTime)          // Aktuální je shodný s požadovaným => není třeba
							return false;
						return this.PullWhenPossible;          // Aktuální je vyšší. Posouvat se bude, když je nastaveno PullWhenPossible.
					case TimeRange.TimeDirection.ToHistory:
						if (currentTime > startTime)
							return true;
						if (currentTime == startTime)
							return false;
						return this.PullWhenPossible;
				}
				return false;
			}
			/// <summary>
			/// Vrátí klon sebe sama, jen pro jiný směr registrace (TimeDir)
			/// </summary>
			/// <param name="timeDir"></param>
			/// <returns></returns>
			internal InteractiveAdjacentArgs CloneForTimeDir(TimeRange.TimeDirection timeDir)
			{
				InteractiveAdjacentArgs clone = (InteractiveAdjacentArgs)this.MemberwiseClone();
				clone.TimeDir = timeDir;
				return clone;
			}
		}
		#endregion
		#region INTERAKTIVNÍ FIXACE OPERACE
		/// <summary>
		/// Metoda, která zajistí nastavení / zrušení fixace pro zvolené elementy.
		/// </summary>
		/// <param name="fixArgs"></param>
		public void InteractiveSetFix(PlanningInteractiveFixArgs fixArgs)
		{
            this.SetPlanningProcessState(PlanningProcessState.NowEditing);             // Nastavím stav Edituji data. Pokud to nejde, vyhodím chybu.

            this._InterArgs = fixArgs;              // Argumenty si uložím do obecné proměnné, budu využívat jen přidání řádků
            try
            {
                this.PlanVariatorPrepare();

                List<DataPointerStr> selectedElements = fixArgs.SelectedElements;
                foreach (DataPointerStr pointer in selectedElements)
                    _InteractiveSetFixOne(pointer, fixArgs.FixSetting);
            }
            finally
            {
                this.PlanVariatorDispose();
                this._InterArgs = null;
            }
		}
		/// <summary>
		/// Metoda provede fixaci pro zvolený element.
		/// </summary>
		/// <param name="pointer">Ukazatel na element</param>
		/// <param name="fixMode">Požadovaný režim fixace</param>
		private void _InteractiveSetFixOne(DataPointerStr pointer, InteractiveFixSetting fixMode)
		{
			bool selectTask = (fixMode & InteractiveFixSetting.SelectedTask) > 0;
			bool selectFinal = (fixMode & InteractiveFixSetting.SelectedFinal) > 0;
			bool setFix = (fixMode & InteractiveFixSetting.Fix) > 0;
			bool setUnfix = (fixMode & InteractiveFixSetting.Unfix) > 0;

			if (!selectTask && !selectFinal) return;        // Pokud nechci ani Task, ani Final, pak neděláme nic.
			if (setFix == setUnfix) return;                 // Pokud chci Fix a současně Unfix, anebo nechci ani jedno, tak skončíme.

			CapacityPlanWorkItemCls task = this._InterFindTaskC(pointer);
			if (task == null) return;

			// Fixace tasku, a případně i příjmové osy:
			this._InterFixSet(task, setFix);

			// Do argumentu fixArgs přidám soupis řádků za každou kapacitní jednotku, jejíž kapacity jsou čerpány tímto taskem
			//  (protože tam se změnila grafika elementu, a pro řádky těchto KPJ se musí přenačíst elementy):
			List<GID> changedRows = task.GetAllPlanUnitCList();
			foreach (GID rowGId in changedRows)
				this._InterArgs.AddChangedRow(rowGId);

			// Do argumentu přidám i GID operace, protože i operace by se měla nově vykreslit:
			this._InterArgs.AddChangedRow(task.TaskGID);

            // Do argumentu přidám i GID řádku osy S, který je příjmem z dané operace, protože na něm je vykreslena operace v grafu poptávek a návrhů:
            this._InterArgs.AddChangedRow(task.AxisData.AxisGIDSubRow);
		}
		/// <summary>
		/// Metoda nastaví příznak fixace na kapacitní úkol a na jeho cílovou osu.
		/// Pokud už daná položka je fixovaná, nechá ji tak.
		/// Pokud je parametr == false, neruší fixaci.
		/// Pokud z této operace (tasku) existuje příjem na osu, pak najde záznam osy S a nastaví fixaci i na něm.
		/// </summary>
		/// <param name="taskID">ID tasku</param>
		/// <param name="addFixedTask">Požadavek na fixaci tasku</param>
		private void _InterFixAdd(int taskID, bool addFixedTask)
		{
			if (!addFixedTask) return;
			this._InterFixSet(taskID, addFixedTask);
		}
		/// <summary>
		/// Metoda nastaví příznak fixace na požadovanou hodnotu pro daný kapacitní úkol, a případně i na jeho cílovou osu.
		/// Tato metoda fixaci nastaví i zruší, podle hodnoty fixValue.
		/// Pokud z této operace (tasku) existuje příjem na osu, pak najde záznam osy S a nastaví / zruší fixaci i na něm.
		/// </summary>
		/// <param name="taskID">ID tasku</param>
		/// <param name="fixValue">Požadavek na fixaci tasku</param>
		private void _InterFixSet(int taskID, bool fixValue)
		{
			CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(taskID);
			this._InterFixSet(task, fixValue);
		}
		/// <summary>
		/// Metoda nastaví příznak fixace na požadovanou hodnotu pro daný kapacitní úkol, a případně i na jeho cílovou osu.
		/// Tato metoda fixaci nastaví i zruší, podle hodnoty fixValue.
		/// Pokud z této operace (tasku) existuje příjem na osu, pak najde záznam osy S a nastaví / zruší fixaci i na něm.
		/// </summary>
		/// <param name="task">Data tasku</param>
		/// <param name="fixValue">Požadavek na fixaci tasku</param>
		private void _InterFixSet(CapacityPlanWorkItemCls task, bool fixValue)
		{
			if (task == null) return;

			// Fix Tasku, paralelních průchodů, navázaných operací VP:
			task.IsFixedTask = fixValue;

            // WorkPass tasku:
            if (!fixValue)
            {
                foreach (WorkPassCls pass in task.WorkPassList)
                    pass.ClearFixedTime();
            }

            // Operace VP, nastavit fixaci do ní, protože pokud bych nyní okamžitě přeplánoval, tak z tasku informace (IsFixedTask) zmizí (protože mizí tasky), a fixace se pak bere z operace VP:
            if (task.CapacType == CapacityUsageType.Real && task.OperationKey > 0)
            {
                ProductOrderOperationCls operation = this.ProductData.FindOperation(task.OperationKey);
                if (operation != null)
                    operation.MfrFixedOperation = (fixValue ? MfrFixedOperationType.Fixed : MfrFixedOperationType.NotFixed);
            }

			// Pokud fixujeme operaci, z níž existují příjmy na osu S, zafixujeme i cílové záznamy na ose S:
			List<PlanningItemRelationCls> relList = this.AxisHeap.FindRelationNextIDData(task.TaskGID, r => (r.Relation == PlanningItemRelationType.TaskToAxisFinal ||
				                                                                                             r.Relation == PlanningItemRelationType.TaskToAxisByProductSuitable ||
				                                                                                             r.Relation == PlanningItemRelationType.TaskToAxisByProductDissonant));
                
			// Fix Axis pro všechny příjmy na osu:
			foreach (PlanningItemRelationCls relItem in relList)
			{
				MaterialPlanAxisItemCls axis = this.AxisHeap.FindAxisSItem(relItem.ItemGIDNext);
				if (axis != null)
					axis.IsFixedAxis = fixValue;
			}
		}
		#endregion
		#region INTERAKTIVNÍ ROZDĚLENÍ PŘÍJMU NA OSU S, PŘEPLÁNOVÁNÍ SMĚREM DOLŮ
		#region ŘÍZENÍ PROCESU
		/// <summary>
		/// Metoda, která zajistí rozdělení jednoho příjmu na osu S na více dílčích příjmů, s definovaným množstvím.
		/// Současně metoda zajistí návazné přeplánování.
		/// </summary>
		/// <param name="splitArgs">Požadavky na rozdělení záznamu osy S</param>
		public void InteractiveSplitAxisS(PlanningInteractiveSplitAxisSArgs splitArgs)
		{
            this.SetPlanningProcessState(PlanningProcessState.NowEditing);             // Nastavím stav Edituji data. Pokud to nejde, vyhodím chybu.

            // Parametr uložím do mé instanční proměnné, protože jej budu potřebovat průběžně používat a hlavně doplňovat:
			this._InterSplitSArgs = splitArgs;
            try
            {
                this.PlanVariatorPrepare();
                this._InterSplitS();
            }
            finally
            {
                this.PlanVariatorDispose();
                this._InterSplitSArgs = null;
            }
		}
		/// <summary>
		/// Top level řízení interaktivního přeplánování osy S
		/// </summary>
		private void _InterSplitS()
		{
			// Vstupní kontroly, načtení CurrTask a FinalAxis :
			this._InterSplitSCheck();

			// Výkon funkce:
			this._InterSplitSRun();
		}
		/// <summary>
		/// Provede kontrolu zadání pro interaktivní přeplánování osy S.
		/// Pokud je zadáno něco neprovedtielného, vyhodí chybu.
		/// Pokud je zadání OK, do out parametrů vloží task a axis.
		/// </summary>
		private void _InterSplitSCheck()
		{
			PlanningInteractiveSplitAxisSArgs splitSArgs = this._InterSplitSArgs;

			DataPointerStr splitPointer = splitSArgs.SplitItemSource;
			// Dohledání operace, záznamu osy S:
			splitSArgs.CurrTaskData = this._InterFindTaskC(splitPointer);
			if (splitSArgs.CurrTaskData == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S byla zadána operace %0, kterou nebylo možno dohledat.", splitPointer.Element));

			splitSArgs.FinalAxisSData = splitSArgs.CurrTaskData.AxisData;
			if (splitSArgs.FinalAxisSData == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S byla zadána operace %0, pro kterou nebylo možno dohledat příjem na osu S.", splitPointer.Element));

			// Kontrola povoleného typu finálního záznamu na ose S:
			bool isPlanIncrement = false;
			switch (splitSArgs.FinalAxisSData.ChangeType)
			{
				case PlanSAxisChangeType.IncrementByPlanProductOrder:
				case PlanSAxisChangeType.IncrementByPlanByProductSuitable:
				case PlanSAxisChangeType.IncrementByPlanByProductDissonant:
					isPlanIncrement = true;
					break;
			}
			if (!isPlanIncrement)
			{
				if (splitSArgs.FinalAxisSData.ChangeType == PlanSAxisChangeType.IncrementByRealProductOrder)
					Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S byla zadána operace %0, kterou nelze rozdělit (jde o operaci výrobního příkazu, a ten zatím v plánování nedělíme)."));
				else
					Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S byla zadána operace %0, kterou nelze rozdělit, jde o pohyb: %1.", splitPointer.Element, PlanningComponentCls.AxisChangeTypeToText(splitSArgs.FinalAxisSData.ChangeType)));
			}
		}
		/// <summary>
		/// Provádí přeregistrování kapacit a změnu plánu pro danou původní osu (návrh na příjem z výroby),
		/// Druh změny: rozdělení návrhu na příjem na více dílčích návrhů na příjem.
		/// </summary>
		private void _InterSplitSRun()
		{
			PlanningInteractiveSplitAxisSArgs splitSArgs = this._InterSplitSArgs;

			// Něco o současném stavu (uložíme do dat argumentu, do internal properties):
			this._InterSplitSReadTaskCData();         // Rozšiřující informace o Tasku, o elementu na který se kliklo (uložíme si informace o tasku a elementu tak, abychom mohli po přeregistraci dohledat nové elementy, odpovídající vstupnímu elementu)
			this._InterSplitSReadLastTaskC();         // Rozšiřující informace o LastTask (operace, časový interval)
			this._InterSplitSReadAxisSData();         // Rozšiřující informace o Axis (skladová karta, záznam o pohybu)

			// Odeberu celou dolní větev výroby, podřízenou cílovému záznamu osy S:
			this._InterSplitSRemoveAllPrev(splitSArgs.FinalAxisSData.AxisGID);

			// Původní záznam osy S o příjmu (Návrh na příjem z výrobního příkazu) rozčlením na požadované podíly, a zaplánuji:
			foreach (PlanningInteractiveSplitAxisSItemArgs splitItem in splitSArgs.SplitItemList)
				this._InterSplitSAddAxis(splitItem);

			// Zajistím doplnánování zbývajících skladových karet (interaktivní doplánování):
			this._RunPlanningProcessRefill();

            // Do argumentu naplním data o nově vzniklých položkách osy S:
            this._InterSplitAddNewAxisSToArgs();
		}
		/// <summary>
		/// Metoda načte informace o výchozím elementu a o tasku, na kterém je vyžadováno rozdělení.
		/// To může být i jiný task, než finální.
		/// Data ukládá do this._InterSplitArgs.
		/// Pokud něco nesouhlasí, vyhodí chybu.
		/// </summary>
		private void _InterSplitSReadTaskCData()
		{
			PlanningInteractiveSplitAxisSArgs splitSArgs = this._InterSplitSArgs;

			// Najdeme WorkUnitCls, který je specifikován požadavkem:
			DataPointerStr splitPointer = splitSArgs.SplitItemSource;
			splitSArgs.CurrWorkUnit = this._InterFindWorkUnit(splitPointer);
			if (splitSArgs.CurrWorkUnit == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S byla zadána operace %0, pro kterou nebylo možno dohledat aktivní element.", splitPointer.Element));

			// Poznamenáme si data o elementu, na který uživatel kliknul (anebo jej poslal jako parametr SplitItemSource):
			splitSArgs.CurrElementLocation = splitSArgs.CurrWorkUnit.WorkUnitLocation;
		}
		/// <summary>
		/// Metoda načte informace o finální operaci výroby, a jejím čase.
		/// Data ukládá do this._InterSplitArgs.
		/// Pokud něco nesouhlasí, vyhodí chybu.
		/// </summary>
		private void _InterSplitSReadLastTaskC()
		{
			PlanningInteractiveSplitAxisSArgs splitSArgs = this._InterSplitSArgs;
			
			splitSArgs.LastTaskData = this.AxisHeap.FindTaskCItem(this.AxisHeap.FindTargetLastTask(splitSArgs.CurrTaskData.TaskGID));
			if (splitSArgs.LastTaskData == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S nelze najít poslední operaci výroby k zadané operaci %0.", splitSArgs.CurrTaskData.TaskGID));
			splitSArgs.LastTaskTime = splitSArgs.LastTaskData.TimeWork;
		}
		/// <summary>
		/// Metoda načte informace o cílovém záznamu osy S, jeho skladové kartě a záznamu o pohybu se změnou na této kartě.
		/// Data ukládá do this._InterSplitArgs.
		/// Pokud něco nesouhlasí, vyhodí chybu.
		/// </summary>
		private void _InterSplitSReadAxisSData()
		{
			PlanningInteractiveSplitAxisSArgs splitSArgs = this._InterSplitSArgs;

			splitSArgs.FinalStockCard = this.MaterialData.GetStockCardOne(splitSArgs.FinalAxisSData.StockUnit);  // Najdu skladovou kartu, na které je osa, kterou budu dělit (na skladové kartě jsou jednotlivé pohyby, které odpovídají záznamům na ose S)
			if (splitSArgs.FinalStockCard == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S nelze najít skladovou kartu pro jednotku %0, na které se změna realizuje..", splitSArgs.FinalAxisSData.StockUnit));
			splitSArgs.FinalStockChangeIndex = splitSArgs.FinalStockCard.FindStockChangeIndex(splitSArgs.FinalAxisSData.AxisID);          // Najdu index záznamu o pohybu na skladové kartě, který odpovídá záznamu osy S (ten se odebere, a na jeho místo se budou insertovat nově vzniklé dílčí příjmy)
			if (splitSArgs.FinalStockChangeIndex < 0)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S nelze najít pohyb o návrhu na příjem na skladové kartě pro jednotku %0, na které se změna realizuje..", splitSArgs.FinalAxisSData.StockUnit));
		}
        /// <summary>
        /// Do argumentu this._InterSplitSArgs doplní data o nově vytvořených řádcích osy S.
        /// </summary>
        private void _InterSplitAddNewAxisSToArgs()
        {
            if (!this.MfrConnectorExists) return;

            MfrPlanningConnectorCls connector = this.MfrConnector;
            List<GID> newRows = this._InterSplitSArgs.NewRows;
            IEnumerable<IDataRow> newDataRows = connector.ReadRowsForAxisS(newRows, true);      // K nově založeným záznamům osy S (=výroba dílců) dohledá i jejich parenta (=výdej z osy do poptávky, jeden element)

            this._InterSplitSArgs.NewRowsDict.Clear();
            this._InterSplitSArgs.AddNewRows(newDataRows);

            // Do pole změněných řádků musím přidat i tyto nové záznamy osy S.
            // Je to kvůli záznamům typu Finální výdej z osy do poptávky: tento záznam se změnil (změna času elementu), a je třeba jej invalidovat (řádek se nebude odebírat a zakládat jako nový).
            // Vizuální vrstva je pak pro invalidované řádky ochotna převzít ze vstupujícího řádku jeho elementy (s aktualizovaným časem):
            foreach (IDataRow iDataRow in newDataRows)
            {   // Přidám jen řádky s ParentGId.RecordNumber == 0 = finální řádky:
                if (iDataRow.ParentGId.AllRecords)
                    this._InterSplitSArgs.AddChangedRow(iDataRow.GId);
            }
        }
        #endregion
		#region ODEBRÁNÍ STÁVAJÍCÍ VĚTVE PLÁNU
		/// <summary>
		/// Metoda pro odebrání specifikované větve plánu, počínaje daným záznamem (GID), 
		/// plus všechny jeho podřízené záznamy (Prev), 
		/// plus malé větve směrem nahoru (příjmy vedlejších produktů z operací: odeberu jen záznam o příjmu na osu S a všechny jeho vztahy, ale ne už navázané záznamy...)
		/// Odebrání záznamů z osy S vede k nastavení skladové karty do stavu Initialized, což následně indikuje nutnost přeplánování této skladové karty.
		/// </summary>
		/// <param name="axisGId">GID výchozího záznamu (Osa S, Task). Tento záznam musí při volání této metody existovat, metoda jej odebere.</param>
		private void _InterSplitSRemoveAllPrev(GID axisGId)
		{
			Queue<GID> itemRemoveQ = new Queue<GID>();
			itemRemoveQ.Enqueue(axisGId);
			List<PlanningItemRelationCls> relList;
			while (itemRemoveQ.Count > 0)
			{
				GID itemRemove = itemRemoveQ.Dequeue();
				// Poznámka: neřeším jednotlivé odebírání vztahů (to se řeší hromadně za celý GID).

				// Strana Next = následující (zajímá mě jen příjem vedlejších produktů):
				relList = this.AxisHeap.FindRelationNextIDData(itemRemove);
				foreach (PlanningItemRelationCls relItem in relList)
				{
					switch (relItem.Relation)
					{
						case PlanningItemRelationType.TaskToAxisByProductSuitable:
						case PlanningItemRelationType.TaskToAxisByProductDissonant:
							// Zruším nadřízený záznam na ose S, ale nebudu realizovat další odebírání.
							// Na ose S jde o příjem. jeho zrušením se zruší o vztahy typu AxisBalance z tohoto příjmu na jeho výdeje (doprava).
							// Skladová karta se označí jako nenaplánovaná (Initialized) a proběhne na ní proces zaplánování.
							// Pokud tedy příjem vedlejšího produktu sloužil jako zajištění pro nějaké výdeje, bude korektně zaplánován.
							// Navíc, po odebrání celé této větve bude zaplánovaná znovu, 
							// takže příjem vedlejšího produktu bude s největší pravděpodobností opět vygenerován při tomto zaplánování.
							this._InterSplitSRemoveItem(relItem.ItemGIDNext);   // Odebere záznam s daným GIDem (Axis / Task).
							break;
					}
				}

				// Strana Prev = předcházející (zajímají mě všechny podřízené záznamy):
				relList = this.AxisHeap.FindRelationPrevIDData(itemRemove);
				foreach (PlanningItemRelationCls relItem in relList)
				{
					switch (relItem.Relation)
					{
						case PlanningItemRelationType.AxisBalance:
							// Zde jde o balancování zásoby v rámci jedné skladové karty. 
							// Tady je rozhodování nejtěžší, svěřím to specializované metodě:
							if (this._InterSplitSIfRemovePrevBalance(relItem))
								itemRemoveQ.Enqueue(relItem.ItemGIDPrev);
							break;
						case PlanningItemRelationType.AxisTransfer:
							// Přenos zásoby z jednoho skladu na jiný 
							//  (na straně Next je příjem, ten odebírám, takže musím odebrat i odpovídající výdej na straně Prev):
							itemRemoveQ.Enqueue(relItem.ItemGIDPrev);
							break;
						case PlanningItemRelationType.AxisToTask:
							// Přechozí záznam je výdej z osy S jakožto komponenta do jisté operace výroby.
							// A protože tuto operaci (task, na straně Next) odebírám, musím odebrat i výdej do operace (Prev).
							itemRemoveQ.Enqueue(relItem.ItemGIDPrev);
							break;
						case PlanningItemRelationType.TaskToAxisFinal:
							// Přechozí záznam je poslední operace, vztah popisuje příjem z poslední operace jako finál na osu S:
							//  v dalším cyklu odeberu záznam Prev (odregistruji poslední task):
							itemRemoveQ.Enqueue(relItem.ItemGIDPrev);
							break;
						case PlanningItemRelationType.TaskToAxisByProductSuitable:
						case PlanningItemRelationType.TaskToAxisByProductDissonant:
							// Předchozí záznam je operace, z níž je navázaný příjem vedlejšího produktu na osu S.
							// Tento příjem na osu S se bude z osy S odebírat (proto jsme tady).
							// Podle mě by se odebírat neměl, nemělo by ani dojít k tomu, že se dostanu do této větve.
							// Dáme hlášku a odmítneme:
							Throw.SysError(MessageInfo.Get("Při odebírání podřízené větve došlo k pokusu o odebrání podřízené operace z důvodu odebrání příjmu vedlejšího produktu, což je chyba."));
							break;
						case PlanningItemRelationType.TaskInner:
							// Přechozí záznam je předchozí operace výroby téhož dílce:
							// v dalším cyklu odeberu záznam Prev (odregistruji task):
							itemRemoveQ.Enqueue(relItem.ItemGIDPrev);
							break;
						case PlanningItemRelationType.TaskTransfer:
							// Sekundární vztah (finální task ~» vyšší dílec, kterýkoli task, vstup finálu jako komponenta): 
							//  neřešíme explicitně, vyřeší se (podle reálné potřeby) při řešení primárních vztahů.
							break;
						case PlanningItemRelationType.AxisOutput:
							// Sekundární vztah (výdej z osy S do poptávky, příjem na osu S pro minimální zásobu, výroba na sklad):
							//  z této strany se na takový vztah nedostaneme, protože vpravo nestojí žádný reálný GID, který by nás sem přivedl.
							break;
					}
				}

				// Odeberu záznam itemRemove:
				this._InterSplitSRemoveItem(itemRemove);
			}
		}
		/// <summary>
		/// Klíčová rozhodovací metoda, která určuje volitelné větvení při odebírání podřízených větví výroby
		/// při odregistrování příjmu na osu S.
		/// Při cestě skrz strom vztahů, tasků a pohybů materiálu jsme narazili na vztah typu AxisBalance, 
		/// jehož stranu Next odebíráme (důvody jsou pro tuto metodu nepodstatné).
		/// Tato metoda musí určit, zda budeme odebírat i záznam na straně Prev, a tím i jeho předchozí záznamy.
		/// Jinými slovy:
		/// Když na ose S zrušíme výdej (například výdej komponenty do návrhu na výrobu, protože tu výrobu předěláváme),
		/// budeme spolu s tím rušit i celý záznam o příjmu, z něhož tento výdej odebírá materiál?
		/// ANO = jen když jde o NÁVRH na příjem, a z tohoto návrhu na příjem je zásoben jen náš výdej.
		/// NE = pokud jde o REÁLNÝ PŘÍJEM (ten z osy neodebíráme !), anebo pokud z tohoto příjmu je zásobeno více než jeden výdej.
		/// Do zásobených výdejů neberu výdej do nikam (výroba na sklad).
		/// </summary>
		/// <param name="relItemTest"></param>
		/// <returns></returns>
		private bool _InterSplitSIfRemovePrevBalance(PlanningItemRelationCls relItemTest)
		{
			if (relItemTest.Relation != PlanningItemRelationType.AxisBalance)
				Throw.SysError(MessageInfo.Get("Chyba řízení: metoda _InterSplitSRemovePrevBalanace() je určena pouze pro řešení vztahů typu «AxisBalance», ale je volána pro vztah «%0».", relItemTest.Relation));

			GID axisGId = relItemTest.ItemGIDPrev;
			// Pokud jako předešlý záznam je Empty, značí to že jde o balancování z výchozí disponibilní zásoby, a tu nebudeme odebírat:
			if (axisGId.IsEmpty)
				return false;

			MaterialPlanAxisItemCls axisPrev = this.AxisHeap.FindAxisSItem(axisGId);
			if (axisPrev == null)
				Throw.SysError(MessageInfo.Get("Chyba řízení: metoda _InterSplitSRemovePrevBalanace() nenašla záznam osy S na straně Prev (GID=%0).", relItemTest.ItemGIDPrev));

			// Jde na straně Prev o NÁVRH na příjem (objednávka, výroba, obecný)?
			bool isIncProp = (
				axisPrev.ChangeType == PlanSAxisChangeType.IncrementByPlanSupplierOrder ||
				axisPrev.ChangeType == PlanSAxisChangeType.IncrementByPlanProductOrder ||
                axisPrev.ChangeType == PlanSAxisChangeType.IncrementByPlanStockTransfer ||
				axisPrev.ChangeType == PlanSAxisChangeType.IncrementByProposalReceipt);
			if (!isIncProp) return false;             // Není to návrh na příjem => nebudeme jej odebírat. (Může to být např. reálný výrobní příkaz, a ten nebudeme měnit!)

			// Podíváme se na to, kam z tohoto návrhu na příjem předáváme jeho přijaté množství
			//  (vztahy typu AxisBalance, doprava z tohoto návrhu na příjem):
            List<PlanningItemRelationCls> relList = this.AxisHeap.FindRelationNextIDData(axisPrev.AxisGID, r => (r.Relation == PlanningItemRelationType.AxisBalance));
			bool isRemovable = true;
			foreach (PlanningItemRelationCls relItem in relList)
			{
				GID nextGId = relItem.ItemGIDNext;
				if (nextGId == relItemTest.ItemGIDNext) continue;     // Předání zásoby do našeho záznamu o výdeji je OK
				if (nextGId.RecordNumber == 0) continue;              // Předání zásoby do nikam (na sklad) nám nevadí
				isRemovable = false;                                  // Jiné záznamy o převodu (do jiného výdeje) způsobí, že návrh na příjem nebude možno odebrat:
				break;
			}
			return isRemovable;
		}
		/// <summary>
		/// Metoda zajistí odebrání jednoho záznamu z osy S (a ze skladové karty) / Tasku (a odregistrování), 
		/// plus odebere veškeré její vztahy (AxisHeap.Relation)
		/// </summary>
		/// <param name="itemGId">GID záznamu k odebrání. Může to být AxisS nebo TaskC.</param>
		private void _InterSplitSRemoveItem(GID itemGId)
		{
			switch (itemGId.ClassNumber)
			{
				case Constants.ClassNumberAxis:
					this._InterSplitSRemoveItemAxisS(itemGId);
					break;
				case Constants.ClassNumberTask:
					this._InterSplitSRemoveItemTaskC(itemGId);
					break;
			}
		}
		/// <summary>
		/// Metoda zajistí odebrání jednoho záznamu z osy S (a ze skladové karty),
		/// plus odebere veškeré její vztahy (AxisHeap.Relation)
		/// </summary>
		/// <param name="itemGId">GID záznamu k odebrání. Může to být jen AxisS.</param>
		private void _InterSplitSRemoveItemAxisS(GID itemGId)
		{
			MaterialPlanAxisItemCls axis = this.AxisHeap.FindAxisSItem(itemGId);
			if (axis == null)
				Throw.SysError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S nelze najít záznam osy S %0 při jednotkovém odebírání záznamů.", itemGId));

			// Odebrat z AxisHeap (AxisS a Relation):
			this.RemovePlanAxisSRecord(itemGId);

			// Zařadit do dat argumentu informace o řádkách, které se smazaly:
			PlanningInteractiveSplitAxisSArgs splitSArgs = this._InterSplitSArgs;
			splitSArgs.AddDeletedRow(itemGId);
			if (axis != null && axis.AxisGIDSubRow.NotEmpty)
				splitSArgs.AddDeletedRow(axis.AxisGIDSubRow);
		}
		/// <summary>
		/// Metoda zajistí odebrání jednoho záznamu tasku (a jeho odregistrování z kapacit),
		/// plus odebere veškeré její vztahy (AxisHeap.Relation)
		/// </summary>
		/// <param name="itemGId">GID záznamu k odebrání. Může to být jen TaskC.</param>
		private void _InterSplitSRemoveItemTaskC(GID itemGId)
		{
			CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(itemGId);
			if (task == null)
				Throw.SysError(MessageInfo.Get("Ve funkci pro rozdělení příjmu na osu S nelze najít kapacitní úkol %0 při jednotkovém odebírání záznamů.", itemGId));

			// Odregistrovat z kapacit 
			//  (při tom dojde díky handleru _InterSplitRemoveWorkUnitHandler() k zapsání informací o změněných řádcích do dat argumentu):
			this.CapacityRegisterData.UnRegisterCapacity(task, this._InterRemoveWorkUnitHandler);

			// Odebrat z AxisHeap (TaskC a Relation):
			PlanningInteractiveSplitAxisSArgs splitSArgs = this._InterSplitSArgs;
			this.AxisHeap.RemovePlanTaskCRecord(itemGId);
			splitSArgs.AddDeletedItem(task.GraphPointerSubAxis);  // Toto je pointer na element (řádek = AxisSubRowGID, element = TaskGID)
		}
		#endregion
		#region ŘÍZENÍ NOVÉHO ZAPLÁNOVÁNÍ VÝROBY V POŽADOVANÉM MNOŽSTVÍ
		/// <summary>
		/// Metoda přidá na hlavní skladovou kartu nový návrh na příjem, který nahrazuje původní návrh na příjem (ten je již odebraný).
		/// Základní informace pro záznam osy S o návrhu na příjem bere z this._InterSplitArgs.FinalAxisSData, kde je uschován původní záznam osy S (ten už ale nikde jinde nenajdeme).
		/// Množství najde v parametru splitItem, kam vloží i výsledek přeregistrace.
		/// Metoda zajistí i zaplánování výrobních operací pro tento návrh na příjem (mělo by totiž jít o návrh na příjem z výrobního příkazu).
		/// Pokud je ale v property CurrentSupplier (v parametru) vyplněn dodavatel, vloží se tento dodavatel na nový záznam osy, a materiál se zajistí objednáním (ze skladu, od dodavatele).
		/// Defaultně je ale splitItem.CurrentSupplier == Empty, takže se zajistí výroba.
		/// </summary>
		/// <param name="splitItem"></param>
		private void _InterSplitSAddAxis(PlanningInteractiveSplitAxisSItemArgs splitItem)
		{
			// Inicializuji prostor pro result:
			splitItem.ResultQty = 0M;
			splitItem.ResultAxisGIdList = new List<GID>();
			splitItem.ResultElementPointerList = new List<DataPointerStr>();

			// Originál osy:
			MaterialPlanAxisItemCls axisOriginal = this._InterSplitSArgs.FinalAxisSData;

			// Vstupující množství nepoužiju přímo, ale upravím jej podle pravidel na skladové kartě 
			//  (avšak podle příznaku splitItem.QtyDisableAdjustment):
			List<decimal> splitQtyList = this._InterSplitSAdjustQty(splitItem);

			foreach (decimal splitQty in splitQtyList)
			{	// Pro každou dílčí maximální dávku:

				// Připravím si nový záznam osy S:
				MaterialPlanAxisItemCls axisProposal = MaterialPlanAxisItemCls.CreateProposalFromProposal(
					axisOriginal, splitItem.QtyRequired, axisOriginal.DatePlanEnd, false, splitItem.CurrentSupplier);

                // Pokud v parametrech položky () je požadavek na nenavyšování množství o ztrátu na dílci, pak to nastavím i do osy S:
                axisProposal.EnableLossCeReflect = splitItem.IsQtyAdjustByElementLoss;

				// Určím T modifikaci STPV pro nový příjem:
				this._InterSplitSAddAxisAOMod(splitItem, ref axisProposal);

				// Vložím záznam osy S do systému (do AxisHeap i do MaterialData):
				this.AddPlanAxisSRecord(axisProposal);

				// Provedu zajištění pro tuto osu S (není to nutně příjem z výroby, mohli jsme určit dodavatele a položka bude nakoupena):
				PlanningScheduleArgs args = this._InterSplitSGetArgsForSplit(splitItem);
				args.RegisterWorkUnitHandler = this._InterAddWorkUnitHandler;       // Register WorkUnit handler
				IEnumerable<PlanningItemRelationCls> relations = this._MaterialResourcingEnsureAndSchedule(axisProposal, args);
				this.AxisHeap.AddItemRelationRange(relations);      // Vztahy typu TaskInner, TaskToAxisXxx, AxisToTask (komponenty). Neproběhlo balancování osy. Nemáme sekundární vztahy. Ty lze najít až po balancování osy.

				// Výsledky do splitItem:
				splitItem.ResultQty += axisProposal.Qty;
				splitItem.ResultAxisGIdList.Add(axisProposal.AxisGID);

                // Zařadit nově přidaný záznam osy S do dat argumentu:
                this._InterSplitSArgs.AddNewRow(axisProposal.AxisGID);

				// Pointer na element musíme najít, to bude fuška:
				DataPointerStr resultElement = this._InterSplitSFindElement(axisProposal.AxisGID);
				splitItem.ResultElementPointerList.Add(resultElement);
			}
		}
		/// <summary>
		/// Metoda připraví argument pro plánování kapacit pro tento podíl výroby.
		/// Reaguje na požadavek dopředného zaplánování poslední operace (splitItem.RegisterTimeDir).
		/// </summary>
		/// <param name="splitItem"></param>
		/// <returns></returns>
		private PlanningScheduleArgs _InterSplitSGetArgsForSplit(PlanningInteractiveSplitAxisSItemArgs splitItem)
		{
			PlanningScheduleArgs args = PlanningScheduleArgs.Default;           // Zpětně
			if (splitItem.RegisterTimeDir == TimeRange.TimeDirection.ToFuture)
			{	// Zadáme specifické časování pro první operaci:
				DateTime lastTaskBegin = this._InterSplitSArgs.LastTaskTime.Begin;
				if (lastTaskBegin > DateTime.MinValue)
					args.SetTimingForFirstTask(splitItem.RegisterTimeDir, lastTaskBegin);
			}
			return args;
		}
		/// <summary>
		/// Metoda připraví T modifikaci na záznam osy S, pokud toho bude zapotřebí.
		/// </summary>
		/// <param name="splitItem">Data pro zaplánování podílu výroby</param>
		/// <param name="axisProposal">Připravená osa S</param>
		private void _InterSplitSAddAxisAOMod(PlanningInteractiveSplitAxisSItemArgs splitItem, ref MaterialPlanAxisItemCls axisProposal)
		{
			if (axisProposal.Acquirement == PlanSAxisAcquirementType.Manufacturing)
			{	// Pokud je způsob zajištění == Výroba, určíme T modifikaci:
				if (splitItem.CurrentAOMod == 0)
					// Pokud není explicitně specifikovaná T modifikace STPV, tak ji najdu standardním postupem:
					this.TpvData.FillMaterialAxisByWH(ref axisProposal, true);          // Doplní T modifikaci, ale ponechá dílec
				else
					// T modifikace STPV je předaná na vstupu: zkusíme ji vložit do návrhu (ověří se, zda přísluší dílci na ose):
					this.TpvData.FillMaterialAxisAOMod(ref axisProposal, splitItem.CurrentAOMod);
			}
		}
		/// <summary>
		/// Připraví údaje o množství, které se bude vyrábět na základě požadavku.
		/// Aplikuje pravidla skladové karty, pokud to v položce splitItem není zakázané.
		/// </summary>
		/// <param name="splitItem">POložka specifikující jedno množství</param>
		/// <returns></returns>
		private List<decimal> _InterSplitSAdjustQty(PlanningInteractiveSplitAxisSItemArgs splitItem)
		{
			if (splitItem.IsQtyAdjustByStockCard)
            {	// Množství uvedené v splitItem.QtyRequired se má upravit standardně podle dat na skladové kartě:
                return this._InterSplitSArgs.FinalStockCard.FindQtyListForProposal(splitItem.QtyRequired);
            }
            else
            {   // Nemá se upravovat podle skladové karty:
				List<decimal> splitQtyList = new List<decimal>();
				splitQtyList.Add(splitItem.QtyRequired);
				return splitQtyList;
			}
		}
		/// <summary>
		/// Metoda najde pointer na element, který nejvíce odpovídá elementu výchozímu, na který uživatel kliknul když chtěl rozdělit výrobu.
		/// Nalezený element by měl pocházet z identické operace - pokud vůbec odpovídající operace vznikly ! 
		/// Taky jsme mohli požádat o jinou technologii (budeme mít jiná čísla operací STPV), 
		/// anebo už vůbec nejde o výrobu (zadali jsme dodavatele, a položku nakoupíme / převedeme ze záložního skladu)
		/// Pokud element nenajdu, vrátím Empty pointer.
		/// </summary>
		/// <param name="axisGId">Záznam osy S o příjmu</param>
		/// <returns></returns>
		private DataPointerStr _InterSplitSFindElement(GID axisGId)
		{
			DataPointerStr pointer = DataPointerStr.Empty;
			WorkUnitLocationCls locator = this._InterSplitSArgs.CurrElementLocation;
			// Najdu operaci odpovídající původní operaci (její klíč = CurrElementLocation.OperationKey) v řadě operací nového dílce (osy S):
			CapacityPlanWorkItemCls currTask = this.AxisHeap.FindTaskCForAxisOper(axisGId.RecordNumber, locator.OperationKey);
			if (currTask != null)
			{	// Pokud máme nalezený task, odpovídající dané operaci:
				WorkUnitCls workUnit = currTask.FindUnitItem(locator);
				pointer = workUnit.DataPointer;
			}
			return pointer;
		}
		#endregion
		#region POMOCNÉ METODY A DATA
		/// <summary>
		/// Argumenty pro řízení interaktivního přeplánování osy S.
		/// Fyzicky se ukládají do obecného úložiště this._InterArgs.
		/// Zde jsou k dispozici přetypované na zdejší konkrétní typ (PlanningInteractiveSplitAxisSArgs).
		/// </summary>
		private PlanningInteractiveSplitAxisSArgs _InterSplitSArgs
		{
			get { return (PlanningInteractiveSplitAxisSArgs)this._InterArgs; }
			set { this._InterArgs = value; }
		}
		#endregion
		#endregion
		#region INTERAKTIVNÍ ROZDĚLENÍ nebo SPOJENÍ PARALELNÍCH PRŮCHODŮ JEDNÉ OPERACE A PŘEPLÁNOVÁNÍ
		#region ŘÍZENÍ PROCESU
		/// <summary>
		/// Metoda, která zajistí rozdělení jednoho paralelního průchodu do vice dílčích průchodů, s definovaným množstvím.
		/// V rámci určité operace (Task).
		/// Současně metoda zajistí návazné přeplánování.
		/// </summary>
		/// <param name="splitArgs">Požadavky na rozdělení záznamu osy S</param>
		public void InteractiveSplitTaskC(PlanningInteractiveSplitTaskCArgs splitArgs)
		{
            this.SetPlanningProcessState(PlanningProcessState.NowEditing);             // Nastavím stav Edituji data. Pokud to nejde, vyhodím chybu.

            // Parametr uložím do mé instanční proměnné, protože jej budu potřebovat průběžně používat a hlavně doplňovat:
			this._InterSplitCArgs = splitArgs;
            try
            {
                this.PlanVariatorPrepare();
                this._InterSplitC();
            }
            finally
            {
                this.PlanVariatorDispose();
                this._InterSplitCArgs = null;
            }
		}
		/// <summary>
		/// Řízení metody pro rozdělení jednoho paralelního průchodu na více menších.
		/// </summary>
		private void _InterSplitC()
		{
			// Vstupní kontroly, načtení CurrTask a FinalAxis :
			this._InterSplitCCheck();

			// Výkon funkce:
			this._InterSplitCRun();
		}
		/// <summary>
		/// Provede kontrolu zadání pro interaktivní přeplánování osy S.
		/// Pokud je zadáno něco neprovedtielného, vyhodí chybu.
		/// Pokud je zadání OK, do out parametrů vloží task a axis.
		/// </summary>
		private void _InterSplitCCheck()
		{
			PlanningInteractiveSplitTaskCArgs splitCArgs = this._InterSplitCArgs;
			DataPointerStr splitPointer = splitCArgs.SplitItemSource;

			// Najdeme WorkUnitCls, který je specifikován požadavkem:
			WorkUnitCls workUnit = this._InterFindWorkUnit(splitPointer);
			if (workUnit == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení operace na paralelní průchody byla zadána operace %0, pro kterou nebylo možno dohledat aktivní element.", splitPointer.Element));
			splitCArgs.CurrWorkUnit = workUnit;

			// Dohledání operace, záznamu osy S:
			CapacityPlanWorkItemCls task = this._InterFindTaskC(splitPointer);
			if (task == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení operace na paralelní průchody byla zadána operace %0, kterou nebylo možno dohledat.", splitPointer.Element));
			splitCArgs.CurrTaskData = task;

			// Najdeme WorkPass, uložíme si referenci na něj:
			WorkPassCls pass = this.FindWorkPass(splitPointer);
			if (pass == null)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení operace na paralelní průchody byl zadán paralelní průchod %0, který nebylo možno dohledat.", splitPointer.Element));
			splitCArgs.CurrWorkPass = pass;
			
			// Kontrola charakteru operace:
			if (task.QtyRequired <= 0M)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení operace na paralelní průchody byla zadána operace %0, na které není žádné množství."));

			// Kontrola shody množství na průchodu a součtu v požadavku:
			decimal diffQty = splitCArgs.SplitItemPrepareVolatileQty(pass.QtyRequired);
			if (diffQty > 0M)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení operace na paralelní průchody je součet množství v položkách (%0) větší než počet na průchodu (%1) (o %2).", splitCArgs.QtyRequiredSum, pass.QtyRequired, diffQty));
			if (diffQty < 0M)
				Throw.AplError(MessageInfo.Get("Ve funkci pro rozdělení operace na paralelní průchody je součet množství v položkách (%0) menší než počet na průchodu (%1) (o %2), a není přítomna žádná položka s pohyblivým množstvím.", splitCArgs.QtyRequiredSum, pass.QtyRequired, diffQty));
		}
		/// <summary>
		/// Provádí přeregistrování kapacit a změnu plánu pro danou původní osu (návrh na příjem z výroby),
		/// Druh změny: rozdělení návrhu na příjem na více dílčích návrhů na příjem.
		/// </summary>
		private void _InterSplitCRun()
		{
			PlanningInteractiveSplitTaskCArgs splitCArgs = this._InterSplitCArgs;

			// Něco o současném stavu (uložíme do dat argumentu, do internal properties):
			this._InterSplitCReadTaskCData();         // Rozšiřující informace o Tasku a Pass, o elementu na který se kliklo (uložíme si informace o tasku a elementu tak, abychom mohli po přeregistraci dohledat nové elementy, odpovídající vstupnímu elementu)

			try
			{
				// Připravíme si seznam všech sousedních WorkUnit, tak aby při přeregistraci kapacit nepřekážely (byly neviditelné):
				this._InteractiveMovePrepare(splitCArgs.CurrWorkUnit);

				// Odeberu kapacity paralelního průchodu, a poté i paralelní průchod z tasku:
				this._InterSplitCRemoveWorkPass();

				// Vytvořím nové průchody a zaregistruji je do kapacit:
				this._InterSplitCCreateRegisterWorkPass();

				// Zajistím přesunutí sousedních operací v obou směrech:
				this._InterSplitCMoveAdjacent();
			}
			finally
			{
				// Zruším seznam položek WorkUnit, který byl sestaven pro jejich zneviditelnění při přeregistraci kapacit:
				this._InteractiveMoveDone();
			}
		}
		/// <summary>
		/// Metoda načte informace o výchozím elementu a o tasku, na kterém je vyžadováno rozdělení.
		/// To může být i jiný task, než finální.
		/// Data ukládá do this._InterSplitArgs.
		/// Pokud něco nesouhlasí, vyhodí chybu.
		/// </summary>
		private void _InterSplitCReadTaskCData()
		{
			PlanningInteractiveSplitTaskCArgs splitCArgs = this._InterSplitCArgs;

			// Načtu si časové úseky Task a Pass, před zahájením změn 
			//  (v průběhu změn se časy bude měnit, protože jejich hodnota se určuje podle WorkTime, a ty se budou odebírat):
			splitCArgs.OriginalTaskTimeRange = splitCArgs.CurrTaskData.TimeWork;
			splitCArgs.OriginalPassTimeRange = splitCArgs.CurrWorkPass.WorkTimeRange;
			
			// Poznamenáme si data o elementu, na který uživatel kliknul (anebo jej poslal jako parametr SplitItemSource):
			splitCArgs.CurrElementLocation = splitCArgs.CurrWorkUnit.WorkUnitLocation;
		}
		#endregion
		#region ODREGISTRCE PARALELNÍHO PRŮCHODU Z KAPACIT, TVORBA NOVÝCH PARALELNÍCH PRŮCHODŮ A JEJICH REGISTRACE DO KAPACIT
		/// <summary>
		/// Odregistruje kapacity paralelního průchodu specifikovaného v parametrech 
		/// </summary>
		private void _InterSplitCRemoveWorkPass()
		{
			PlanningInteractiveSplitTaskCArgs splitCArgs = this._InterSplitCArgs;

			// Odregistrovat z kapacit 
			WorkPassCls workPass = splitCArgs.CurrWorkPass;

			// Při tom dojde díky handleru _InterSplitRemoveWorkUnitHandler() k zapsání informací o změněných řádcích do dat argumentu:
			//  a argumentem (false) i k odebrání prázdné slupky paralelního průchodu z jeho parenta = Task:
			this.CapacityRegisterData.UnRegisterCapacity(workPass, this._InterRemoveWorkUnitHandler, false);
		}
		/// <summary>
		/// Vytvoří a zaregistruje nové paralelní průchody do kapacit
		/// </summary>
		private void _InterSplitCCreateRegisterWorkPass()
		{
			PlanningInteractiveSplitTaskCArgs splitCArgs = this._InterSplitCArgs;
			CapacityPlanWorkItemCls task = splitCArgs.CurrTaskData;
			TimeRange originalTimeRange = splitCArgs.OriginalPassTimeRange;

			// Vytvoříme jednotlivé paralelní průchody s množstvím požadovaným, odvozujeme množství Finální pro jednotlivé Pass:
			List<PlanningInteractiveSplitTaskCItemArgs> splitList = splitCArgs.SplitItemList;
			CapacityRegisterArgs regArgs = CapacityRegisterArgs.Create(this._InterAddWorkUnitHandler);
            decimal taskQtyFinal = task.QtyFinal;
            List<decimal> splitQtyList = splitList.ConvertAll(i => i.QtyWorkPass);
            int roundRoot = GetRoundRoot(splitQtyList);
            decimal splitQtySum = splitQtyList.Sum();
            decimal ratioFinal = taskQtyFinal / splitQtySum;

            int splitCount = splitList.Count;
			for (int s = 0; s < splitCount; s++)
			{
                PlanningInteractiveSplitTaskCItemArgs splitItem = splitList[s];
                bool isLast = (s == (splitCount - 1));
                // Nový paralelní průchod, množství požadované, množství finální:
				decimal passQtyRequired = splitItem.QtyWorkPass;
                decimal passQtyFinal = (isLast ? taskQtyFinal : Math.Round(ratioFinal * passQtyRequired, roundRoot));      // Do posledního Pass předám zbývající množství taskQtyFinal, jinak zaokrouhlenou poměrnou část
                WorkPassCls workPass = task.InsertNewWorkPass(passQtyRequired, passQtyFinal);
                taskQtyFinal -= passQtyFinal;                                   // Snižuji k nule po každém vytvořeném Pass

				// Timing, registrace kapacit:
				this._InterSplitCTimingNewWorkPass(workPass, splitItem.RegisterTimeVector, originalTimeRange);
				this.CapacityRegisterData.RegisterCapacityForITask(workPass, regArgs);

				// Pointer na element musíme najít, to bude fuška:
				DataPointerStr resultElement = this._InterSplitCFindElement(workPass);
				splitItem.ResultElementPointerList.Add(resultElement);
			}
            task.CapacityBatchRecalc();
		}
		/// <summary>
		/// Do paralelního průchodu vloží časové parametry pro registraci kapacit
		/// </summary>
		/// <param name="workPass">Paralelní průchod</param>
		/// <param name="timeVector">Požadovaný vektro</param>
		/// <param name="originalTimeRange">Původní časový interval</param>
		private void _InterSplitCTimingNewWorkPass(WorkPassCls workPass, TimeVector timeVector, TimeRange originalTimeRange)
		{
            TimeRange.TimeDirection timeDir = timeVector.Direction;             // Směr registrace kapacity pro tento Pass
            DateTime startTime = timeVector.Date;                               // Výchozí datum
            if (startTime == DateTime.MinValue)                                 // Pokud je nezadané, vezmu jej z původního času průchodu,
				startTime = originalTimeRange.Edge(timeDir, true);              //  z jeho hrany odpovídající počátku ve směru času.
			workPass.SetTiming(timeDir, OperationPhase.None, startTime, true);  // Všechno vložím do Pass. Fáze = None => standardní registrace.
		}
		/// <summary>
		/// Metoda najde pointer na element, který nejvíce odpovídá elementu výchozímu, na který uživatel kliknul když chtěl rozdělit výrobu.
		/// Nalezený element bude pocházet z téže operace, z nově vytvořeného průchodu, ze shodné pozice času (fáze, zdroj, jednotka).
		/// Pokud element nenajdu, vrátím Empty pointer.
		/// </summary>
		/// <param name="workPass">Objekt s daty paralelního průchodu</param>
		/// <returns></returns>
		private DataPointerStr _InterSplitCFindElement(WorkPassCls workPass)
		{
			DataPointerStr pointer = DataPointerStr.Empty;
			WorkUnitLocationCls locator = this._InterSplitCArgs.CurrElementLocation;
			WorkUnitCls workUnit = workPass.FindUnitItem(locator);          // WorkPass vyhledá odpovídající WorkUnit
			if (workUnit != null)
				pointer = workUnit.DataPointer;
			return pointer;
		}
		/// <summary>
		/// Metoda zajistí posunutí sousedních záznamů (tasky, osa S) tak, aby výroba mohla nerušeně pokračovat
		/// </summary>
		private void _InterSplitCMoveAdjacent()
		{
			PlanningInteractiveSplitTaskCArgs splitCArgs = this._InterSplitCArgs;

			// Zajistím přesunutí sousedních záznamů. pokud to je zapotřebí:
			this._InteractiveShiftAdjacent(splitCArgs.CurrTaskData.TaskGID, InteractiveAdjacentArgs.CreateForTree(TimeRange.TimeDirection.ToFuture, splitCArgs));
			this._InteractiveShiftAdjacent(splitCArgs.CurrTaskData.TaskGID, InteractiveAdjacentArgs.CreateForTree(TimeRange.TimeDirection.ToHistory, splitCArgs));
		}
		#endregion
		#region POMOCNÉ METODY A DATA
		/// <summary>
		/// Argumenty pro řízení interaktivního přeplánování jednoho tasku
		/// </summary>
		private PlanningInteractiveSplitTaskCArgs _InterSplitCArgs
		{
			get { return (PlanningInteractiveSplitTaskCArgs)this._InterArgs; }
			set { this._InterArgs = value; }
		}
		#endregion
		#endregion
		#region INTERAKTIVNÍ LINKOVÁNÍ OPERACÍ, ZRUŠENÍ LINKOVÁNÍ
		/// <summary>
		/// Metoda, která zajistí nastavení / zrušení linkování pro zvolené elementy.
		/// </summary>
		/// <param name="linkArgs">Požadavky na linkování</param>
		public void InteractiveSetLink(PlanningInteractiveLinkArgs linkArgs)
		{
			if (linkArgs.LinkSetting == InteractiveLinkSetting.NoChange)
				return;

            this.SetPlanningProcessState(PlanningProcessState.NowEditing);             // Nastavím stav Edituji data. Pokud to nejde, vyhodím chybu.

            this._InterArgs = linkArgs;              // Argumenty si uložím do obecné proměnné, budu využívat jen přidání řádků

            try
            {
                this.PlanVariatorPrepare();

                // Unikátní seznam tasků podle zvolených elementů:
                List<CapacityPlanWorkItemCls> selectedTasks = this.FindTaskCUniqueList(linkArgs.SelectedElements);
                this._InteractiveSetLinkPrepareLink(linkArgs, selectedTasks);     // Připravím objekt / číslo Link, podle režimu a podle zadání a dat.

                // Pro každý task vepíšu Link:
                foreach (CapacityPlanWorkItemCls task in selectedTasks)
                    this._InteractiveSetLinkOne(task, linkArgs);

                // Resetuji cache Linked, a až bude potřeba, pak si ji systém vygeneruje:
                this.LinkedElementListReset();
                linkArgs.ResultElementLinkListChanged = true;
            }
            finally
            {
                this.PlanVariatorDispose();
                this._InterArgs = null;
            }
		}
		/// <summary>
		/// Do daného tasku vepíše daný Link.
		/// Podle režimu tento task vepíše i do případných sousedních tasků tohoto tasku
		/// (to při scelení skupiny v režimu InteractiveLinkSetting.LinkToCollective).
		/// </summary>
		/// <param name="task"></param>
		/// <param name="linkArgs"></param>
		private void _InteractiveSetLinkOne(CapacityPlanWorkItemCls task, PlanningInteractiveLinkArgs linkArgs)
		{
			int currLinkGroup = task.LinkGroup;             // Současná skupina tohoto tasku
			if (currLinkGroup != linkArgs.LinkGroup)
			{
				if (linkArgs.LinkSetting == InteractiveLinkSetting.LinkToCollective && currLinkGroup != 0)
				{	// Pokud se má skupina scelovat, a původní skupina existuje a je jiná než nová:
					// najdu všechny tasky původní skupiny (daný task v ní bude taky):
					List<CapacityPlanWorkItemCls> taskCollList = this.FindTaskCLinkedList(currLinkGroup);
					foreach (CapacityPlanWorkItemCls taskColl in taskCollList)
					{
						this._InteractiveSetLinkToTask(taskColl, linkArgs);
					}
				}
				else
				{	// Vložit link z argumentu do daného tasku:
					this._InteractiveSetLinkToTask(task, linkArgs);
				}
			}
		}
		/// <summary>
		/// Do daného tasku vloží informace o linkování
		/// </summary>
		/// <param name="task"></param>
		/// <param name="linkArgs"></param>
		private void _InteractiveSetLinkToTask(CapacityPlanWorkItemCls task, PlanningInteractiveLinkArgs linkArgs)
		{
			if (linkArgs.LinkObject != null)
			{
				task.LinkObject = linkArgs.LinkObject;         // v set metodě vloží:   task.LinkRecordNumber = SqlInt32.Null;
			}
			else if (!linkArgs.LinkRecordNumber.IsNull)
			{
				task.LinkObject = null;                        // vložím null, tím otevřu možnost vložit task.LinkRecordNumber. Jinak dojde k chybě.
				task.LinkRecordNumber = linkArgs.LinkRecordNumber;
			}
			else
			{
				task.LinkObject = null;
				task.LinkRecordNumber = SqlInt32.Null;
			}
			this._InteractiveArgsAddChangedRows(task);
		}
		/// <summary>
		/// Do argumentu vloží GID změněných řádků (task, elementy linků = kapacitní jednotky)
		/// </summary>
		/// <param name="task"></param>
		private void _InteractiveArgsAddChangedRows(CapacityPlanWorkItemCls task)
		{
			this._InterArgs.AddChangedRow(task.TaskGID);
			this._InterArgs.AddChangedRows(task.GetAllPlanUnitCList());
		}
		/// <summary>
		/// Podle požadavků a podle dat připraví linkovací objekt.
		/// </summary>
		/// <param name="linkArgs">Požadavky na linkování</param>
		/// <param name="selectedTasks">Unikátní seznam tasků</param>
		private void _InteractiveSetLinkPrepareLink(PlanningInteractiveLinkArgs linkArgs, List<CapacityPlanWorkItemCls> selectedTasks)
		{
			// Zde musím určit objekt nebo alespoň číslo skupiny:
			IRecord linkObject = null;
			SqlInt32 linkRecordNumber = SqlInt32.Null;

			switch (linkArgs.LinkSetting)
			{
				case InteractiveLinkSetting.NoChange:
					// NoChange - nic:
					return;

				case InteractiveLinkSetting.UnLink:
					// Unlink - null:
					linkArgs.LinkObject = null;
					linkArgs.LinkRecordNumber = SqlInt32.Null;
					return;

				case InteractiveLinkSetting.LinkToCollective:
					// LinkToCollective - najít první kolektiv, do něhož se spojí všechny skupiny:
					foreach (CapacityPlanWorkItemCls task in selectedTasks)
					{
						if (task.LinkObject != null)
						{	// Pokud některý task má svůj objekt, převezmu jej jako společný (kolektivní), a dosavadní nalezené číslo zahodím:
							linkObject = task.LinkObject;
							linkRecordNumber = SqlInt32.Null;
							break;
						}
						if (linkRecordNumber.IsNull && !task.LinkRecordNumber.IsNull)
							// Pokud má task jen číslo, a ne objekt, vezmu to číslo - ale zkusím hledat v dalším tasku, zda nemá objekt (ten mám radši):
							linkRecordNumber = task.LinkRecordNumber;
					}
					// Pokud jsme nic nenašli (žádný task není členem nějaké skupiny),
					//  pak vezmu zavděk daty z argumentu:
					if (linkObject == null && linkArgs.LinkObject != null)
						linkObject = linkArgs.LinkObject;
					else if (linkObject == null && linkRecordNumber.IsNull && !linkArgs.LinkRecordNumber.IsNull)
						linkRecordNumber = linkArgs.LinkRecordNumber;

					break;

				case InteractiveLinkSetting.LinkToNewSeparate:
					// LinkToNewSeparate - vždy bude cílem nová skupina:
					linkObject = linkArgs.LinkObject;
					if (linkObject == null && !linkArgs.LinkRecordNumber.IsNull)
						linkRecordNumber = linkArgs.LinkRecordNumber;

					break;
			}

			// Pokud jsme neurčili ani číslo skupiny linkObjectNumber, pak založím nový objekt:
			if (linkObject == null && linkRecordNumber.IsNull)
			{
				int folderNumber = linkArgs.LinkFolderNumber;
				linkObject = CreateNewDefaultLinkObject(ref folderNumber);
				linkArgs.LinkFolderNumber = folderNumber;
			}

			// Nalezená data vepíšu do argumentu:
			linkArgs.LinkObject = linkObject;
			linkArgs.LinkRecordNumber = (linkObject != null ? SqlInt32.Null : linkRecordNumber);
		}
		/// <summary>
		/// Metoda vygeneruje a vrátí nový objekt pro propojení skupiny linkovaných operací.
		/// Objekt založí do daného pořadače.
		/// Pokud nebude zadáno číslo pořadače, systém si najde první pořadač podle definice Noris vztahu : 
		/// 103313 - Vazby společných oper. (statický, ze třídy Kapacitní úkol, dbname lcs.plan_unit_c_task.link).
		/// Číslo použitého pořadače bude uloženo do ref parametru.
		/// </summary>
		/// <param name="folderNumber">Číslo pořadače. Pokud je zadaný, musí vyhovovat. Může být 0.
		/// Musí vyhovovat vztahu 103313 - Vazby společných oper. (statický, ze třídy Kapacitní úkol, dbname lcs.plan_unit_c_task.link).
		/// Musí to být pořadač třídy 13: Obecný subjekt.
		/// </param>
		/// <returns></returns>
		public Noris.Schedule.Support.Data.IRecord CreateNewDefaultLinkObject(ref int folderNumber)
		{
			FolderInfo relFolder = null;

			// Třída kapacitních úkolů, její vztahy, vztah "link":
			ClassInfo classTask = Repository.Classes[PlanUnitCTaskCls.ClassNr];
			RelationInfo relLink = classTask.RelationLeftList.Find(r => String.Equals(r.ColumnName, "lcs.plan_unit_c_task.link", StringComparison.OrdinalIgnoreCase));
			if (relLink == null)
				Throw.AplError(MessageInfo.Get("Není nalezen vztah 103313 - «Vazby společných oper.» ze třídy kapacitního úkolu na spojovací záznam (link)."));
			List<FolderInfo> foldList = relLink.FoldersList;           // Seznam cílových pořadačů pro tento vztah

			// Zkusíme najít zadané číslo pořadače:
			if (folderNumber > 0)
			{
				// Zadaný pořadač musí existovat, a obsahovat data třídy Obecný subjekt:
				FolderInfo inpFolder = Repository.Folders[folderNumber];
				if (inpFolder == null)
					Throw.AplError(MessageInfo.Get("Pořadač %0 (zadaný jako pořadač záznamů pro Vazby společných operací) neexistuje.", folderNumber));
				if (inpFolder.CisloTridy != SubjectCls.ClassNr)
					Throw.AplError(MessageInfo.Get("Pořadač %0: %1 (pro Vazby společných operací) je jiné třídy (%2: %3) než Obecný subjekt.", folderNumber, inpFolder.Nazev, inpFolder.CisloTridy, inpFolder.ClassData.NazevTridy));
				// ... a musí být uveden jako cílový ve vztahu "lcs.plan_unit_c_task.link":
				relFolder = foldList.Find(f => (f.CisloPoradace == inpFolder.CisloPoradace));
				if (inpFolder == null)
					Throw.AplError(MessageInfo.Get("Pořadač %0: %1 (pro Vazby společných operací) není uveden jako platný pořadač pro vztah 103313 - «Vazby společných oper.».", folderNumber, inpFolder.Nazev));
			}

			// Pořadač nebyl zadaný - najdeme výchozí:
			if (relFolder == null)
			{
				// První cílový pořadač:
				relFolder = foldList.Find(f => f.CisloTridy == SubjectCls.ClassNr); // Najdu pořadač, jehož třída je Obecný subjekt.
				if (relFolder == null)
					Throw.AplError(MessageInfo.Get("Vztah %0 %1 nemá mezi cílovými pořadači žádný pořadač třídy Obecný subjekt.", relLink.CisloVztahu, relLink.Nazev));
			}

			// Nový obecný subjekt, vygenerovat referenci:
			SubjectCls subject = new SubjectCls(true);
			folderNumber = relFolder.CisloPoradace;
			subject.ResetForFolder(folderNumber);

			return subject;
		}
		#endregion
		#region DOHLEDÁNÍ ZÁZNAMŮ OSY S, TASKU, WORK PODLE DAT GRAFICKÉ TABULE (typicky DataPointer)
		/// <summary>
        /// Metoda najde a vrátí task na základě předaného pointeru.
        /// POZOR: pointer pochází z grafické tabule, a může obsahovat různé datové třídy, je třeba je detekovat!
        /// </summary>
		/// <param name="pointer">Pointer na element, jehož task chceme vrátit</param>
		/// <returns></returns>
		public CapacityPlanWorkItemCls FindTaskC(DataPointerStr pointer)
		{
			return this._InterFindTaskC(pointer);
		}
        /// <summary>
        /// Metoda najde a vrátí task na základě předaného GIDu.
        /// GID pochází z grafické tabule a může obsahovat různé datové třídy, je třeba je detekovat!
        /// </summary>
        /// <param name="taskGId">GID tasku</param>
        /// <returns></returns>
        public CapacityPlanWorkItemCls FindTaskC(GID gid)
        {
            return this._InterFindTaskC(gid);
        }
        /// <summary>
		/// Metoda najde a vrátí unikátní seznam tasků podle seznamu elementů (DataPointer).
		/// Pro každý DataPointer najde task, a pokud je to první výskyt pak jej zařadí do výstupního seznamu.
		/// </summary>
		/// <param name="pointers">Seznam pointerů na element, jejichž tasky chceme vrátit</param>
		/// <returns></returns>
		public List<CapacityPlanWorkItemCls> FindTaskCUniqueList(IEnumerable<DataPointerStr> pointers)
		{
			Dictionary<GID, CapacityPlanWorkItemCls> taskDict = new Dictionary<GID, CapacityPlanWorkItemCls>();
			if (pointers != null)
			{
				foreach (DataPointerStr pointer in pointers)
				{
					CapacityPlanWorkItemCls task = this._InterFindTaskC(pointer);
					if (task != null && !taskDict.ContainsKey(task.TaskGID))
						taskDict.Add(task.TaskGID, task);
				}
			}
			return new List<CapacityPlanWorkItemCls>(taskDict.Values);
		}
		/// <summary>
		/// Metoda najde a vrátí seznam tasků, které jsou v jedné skupině linku.
		/// </summary>
		/// <param name="linkGroup">Číslo skupiny</param>
		/// <returns></returns>
		public List<CapacityPlanWorkItemCls> FindTaskCLinkedList(int linkGroup)
		{
			List<CapacityPlanWorkItemCls> result = this.AxisHeap.FilterTask(t => (t.LinkGroup == linkGroup));
			return result;
		}
		/// <summary>
		/// Metoda najde a vrátí paralelní průchod podle hodnot v DataPointeru
		/// </summary>
		/// <param name="pointer">Pointer na element, jehož pass chceme vrátit. Musí se jednat o pointer na WorkUnit.</param>
		/// <returns></returns>
		public WorkPassCls FindWorkPass(DataPointerStr pointer)
		{
			WorkUnitCls workUnit = this.FindWorkUnit(pointer);
			if (workUnit == null) return null;
			return workUnit.WorkPass;
		}
		/// <summary>
        /// Metoda najde a vrátí jednotku práce (WorkUnit) podle jejího klíče DataPointerStr.
		/// </summary>
		/// <param name="pointer">Pointer na element, jehož pass chceme vrátit. Musí se jednat o pointer na WorkUnit.</param>
		/// <returns></returns>
		public WorkUnitCls FindWorkUnit(DataPointerStr pointer)
		{
			if (pointer.Element.ClassNumber != Constants.ClassNumberWork) return null;
			return this.AxisHeap.FindIWorkItem(pointer.Element);
		}
        /// <summary>
        /// Metoda najde a vrátí jednotku práce (WorkUnit) podle jejího klíče ID.
        /// </summary>
        /// <param name="pointer">Pointer na element, jehož pass chceme vrátit. Musí se jednat o pointer na WorkUnit.</param>
        /// <returns></returns>
        public WorkUnitCls FindWorkUnit(int workID)
        {
            return this.AxisHeap.FindIWorkItem(workID);
        }
        /// <summary>
		/// Metoda najde a vrátí task na základě předaného pointeru.
		/// POZOR: pointer pochází z grafické tabule, a může obsahovat různé datové třídy, je třeba je detekovat!
		/// </summary>
		/// <param name="pointer">pointer na task, využívá se jeho elementu</param>
		/// <returns></returns>
        private CapacityPlanWorkItemCls _InterFindTaskC(DataPointerStr pointer)
        {
            return this._InterFindTaskC(pointer.Element);
        }
		/// <summary>
		/// Metoda najde a vrátí task na základě předaného GIDu.
        /// GID pochází z grafické tabule a může obsahovat různé datové třídy, je třeba je detekovat!
		/// </summary>
		/// <param name="gid">GID tasku</param>
		/// <returns></returns>
        private CapacityPlanWorkItemCls _InterFindTaskC(GID gid)
		{
			CapacityPlanWorkItemCls task = null;
			switch (gid.ClassNumber)
			{
				case Constants.ClassNumberTask:
					// Na vstupu je element operace (například v Gantt grafu jako konkrétní operace):
					task = this.AxisHeap.FindTaskCItem(gid);
					break;
				case Constants.ClassNumberWork:
					// Na vstupu je TaskLink = úsek práce na grafu využití kapacit:
                    WorkUnitCls work = this.AxisHeap.FindIWorkItem(gid);
					if (work != null && work.TaskData != null)
						task = work.TaskData;
					break;
			}
			return task;
		}
		/// <summary>
		/// Metoda najde a vrátí WorkUnit na základě předaného pointeru.
		/// POZOR: pointer pochází z grafické tabule, a může obsahovat různé datové třídy, je třeba je detekovat!
		/// </summary>
		/// <param name="pointer"></param>
		/// <returns></returns>
		private WorkUnitCls _InterFindWorkUnit(DataPointerStr pointer)
		{
			WorkUnitCls work = null;

			DataPointerStr workPointer = DataPointerStr.Empty;
			switch (pointer.Element.ClassNumber)
			{
				case Constants.ClassNumberTask:
					// Na vstupu je Task (operace) = najdeme si první viditelný element této operace:
					CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(pointer.Element);
					if (task != null)
						workPointer = task.GetOuterPointer(TimeRange.TimeDirection.ToHistory, false, true);
					break;
				case Constants.ClassNumberWork:
					// Na vstupu je TaskLink = úsek práce na grafu využití kapacit:
					workPointer = pointer;
					break;
			}

			if (!workPointer.IsEmpty && workPointer.Element.ClassNumber == Constants.ClassNumberWork)
				work = this.AxisHeap.FindIWorkItem(workPointer.Element);

			return work;
		}
		#endregion
		#region OBECNÉ POMOCNÉ METODY A DATA (AddWork(), RemoveWork(), property InterArgs)
		/// <summary>
		/// Metoda zajistí přidání pracovní jednotky do dat plánovacího procesu, a navíc tento fakt zaregistruje do dat pro vizuální proces.
		/// </summary>
		/// <param name="workUnit"></param>
		private void _InterAddWorkUnitHandler(WorkUnitCls workUnit)
		{
			this._InterArgs.AddNewItem(workUnit);           // Zajistí přidání WorkUnit do vizuální tabule (přes parametr)
			this.AxisHeap.AddWorkUnitRecord(workUnit);      // Zajistí přidání WorkUnit do dat plánovacího procesu
		}
		/// <summary>
		/// Metoda zajistí odebrání pracovní jednotky z dat plánovacího procesu, a navíc tento fakt zaregistruje do dat pro vizuální proces.
		/// </summary>
		/// <param name="workUnit"></param>
		private void _InterRemoveWorkUnitHandler(WorkUnitCls workUnit)
		{
			this._InterArgs.AddDeletedItem(workUnit);       // Zajistí odebrání WorkUnit z vizuální tabule (přes parametr)
			this.AxisHeap.RemoveWorkUnitRecord(workUnit);   // Zajistí odebrání WorkUnit z dat plánovacího procesu
		}
		/// <summary>
		/// Úložiště pro argumenty pro řízení interaktivních procesů. Typicky se sem ukládá některý z potomků třídy PlanningInteractiveArgs.
		/// S touto referencí pracují obecné metody (_InterAddWorkUnitHandler(), _InterRemoveWorkUnitHandler()).
		/// </summary>
		private PlanningInteractiveArgs _InterArgs;
		#endregion
	}
}
